--[[ Pillar Chase - Role Highlight Hub (v2) Place 121255181515820 (Pillar Chase 2 / Halloween PT1) UI Dark "Product Faker"-style window: Roboto Condensed typography, near black panels, a centred floating window with shadow + hairline stroke, a bottom icon rail with a sliding indicator, toast notifications and scramble / fade / scale animations. Seven tabs: Roles, Visuals, HUD, Tasks, Teleport, Extras, Info. FEATURES * Role highlights: Monster=red, Survivor=green, Sentinel=amber (+Unknown) * See-through-walls, fill opacity, per-role colour presets * Name / role / distance labels * Off-screen direction arrows, tracers, monster proximity alert * Always-on status HUD (survivors alive, monster distance, your role) * Tasks: objective ESP on every map + auto-collect. It scans the map and round FIRST; if you are not in a game (no map) or you are dead it does nothing and the readout says "Not in game.". Collection handles both touch shards and ProximityPrompt objectives (snow, trash, blood, keypads, valves, generators). * Utility: anti-AFK, fullbright, no fog, noclip, FOV zoom, exit ESP, crosshair, fps/ping * Unlocks the mouse while the window is open (the game force-locks it, which is why buttons were unclickable), restored automatically SAFETY Everything here is LOCAL: Highlight, BillboardGui and the overlay are never replicated, the server never sees them, and no game logic, movement or hit detection is touched. No hooks, no remotes. ROLE DETECTION (verified live) Character:GetAttribute("Type") == "Monster" / "Survivor" / "Sentinel" Player:GetAttribute("EndCondition") == "MONSTER" (backup) Anything else -> "Unknown" (never silently hidden). ]] if game.PlaceId ~= 121255181515820 then return end -- single instance: if a previous build of this hub is still running (e.g. an -- autoexec copy plus a manual run), tear it down first so two windows never -- fight over the same key and the same scratch folders. do local prev = getgenv and getgenv().PillarChaseHub if prev and type(prev.destroy) == "function" then pcall(prev.destroy) end end local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local RunService = game:GetService("RunService") local CoreGui = game:GetService("CoreGui") local Workspace = workspace local LP = Players.LocalPlayer local genv = (type(getgenv) == "function" and getgenv()) or _G -- replace a previous instance from this session (never stack on re-run) local previous = genv.PillarChaseHub if type(previous) == "table" and type(previous.destroy) == "function" then pcall(previous.destroy) end -- ========================================================================== -- state -- ========================================================================== local DEFAULTS = { enabled = true, throughWalls = true, fillTransparency = 0.55, outlineTransparency = 0.05, labels = true, showRole = true, showDistance = true, maxDist = 5000, self = false, freeMouse = true, hud = true, tracers = false, offscreen = true, monsterAlert = true, alertDist = 150, shardEsp = true, shardColor = { 90, 240, 255 }, autoCollect = false, collectDelay = 0.1, collectHeight = 2, -- utility infiniteStamina = true, antiAfk = true, fullbright = false, noFog = false, noclip = false, speedMult = 1, cameraFov = 70, exitEsp = true, exitColor = { 255, 140, 40 }, crosshair = false, crosshairColor = { 120, 255, 160 }, fpsCounter = true, pingCounter = true, roles = { Monster = true, Survivor = true, Sentinel = true, Unknown = false, }, colors = { Monster = { 255, 45, 60 }, Survivor = { 60, 220, 110 }, Sentinel = { 255, 190, 40 }, Unknown = { 170, 175, 190 }, Self = { 80, 200, 255 }, }, keybind = "K", } local STATE = {} for k, v in pairs(DEFAULTS) do if type(v) == "table" then local t = {} for k2, v2 in pairs(v) do t[k2] = (type(v2) == "table") and { v2[1], v2[2], v2[3] } or v2 end STATE[k] = t else STATE[k] = v end end local CONFIG_FILE = "pillar-chase-hub.json" local function deepCopy(t) local out = {} for k, v in pairs(t) do out[k] = (type(v) == "table") and deepCopy(v) or v end return out end local function saveConfig() pcall(function() if type(writefile) ~= "function" then return end local function ser(v) local tv = type(v) if tv == "number" or tv == "boolean" then return tostring(v) elseif tv == "string" then return string.format("%q", v) elseif tv == "table" then local parts = {} for k, vv in pairs(v) do -- numeric keys (e.g. colour channels {255,45,60}) must stay -- numeric, otherwise they reload as c["1"] and every colour -- silently breaks. local key if type(k) == "number" then key = string.format("[%d]", k) else key = string.format("[%q]", tostring(k)) end parts[#parts + 1] = key .. "=" .. ser(vv) end return "{" .. table.concat(parts, ",") .. "}" end return "nil" end writefile(CONFIG_FILE, "return " .. ser(STATE)) end) end local function loadConfig() pcall(function() if type(isfile) ~= "function" or type(readfile) ~= "function" then return end if not isfile(CONFIG_FILE) then return end local fn = loadstring(readfile(CONFIG_FILE)) if not fn then return end local saved = fn() if type(saved) ~= "table" then return end for k, v in pairs(saved) do if type(v) == "table" and type(STATE[k]) == "table" then for k2, v2 in pairs(v) do -- heal old configs where numeric keys were saved as strings local key = k2 if type(k2) == "string" then local num = tonumber(k2) if num and tostring(num) == k2 then key = num end end STATE[k][key] = v2 end else STATE[k] = v end end end) end loadConfig() -- ========================================================================== -- UI toolkit · dark "Product Faker"-style skin -- * Roboto Condensed typography, near-black panels -- * centred floating window with shadow + hairline stroke -- * bottom icon rail with a sliding active indicator -- * toast notifications, scramble text, fade + scale animations -- ========================================================================== -- -------------------------------------------------------------------------- -- The UI layer is wrapped in a `do` block: its many one-off helpers must not -- stay alive for the whole chunk (Luau allows 200 locals per function). Only -- the names below are needed by the engines further down. -- -------------------------------------------------------------------------- local THEME, rgb, new, corner, stroke, parentGui local gui, win, Main, topbar, statusDot, selectTab, showWindow, hideWindow, closeNotice local connections, colorSyncers, collectOneShard, hud local infoStatus, infoMap, infoObj1, infoObj2, infoExit, infoShards local infoRole, infoMonster, infoSurvivors, infoTracked, infoMouse local keybindBtn, keybindCapturing, bannerText, bannerMap, bannerDot local TP do local TweenService = game:GetService("TweenService") local TextService = game:GetService("TextService") THEME = { bg = Color3.fromRGB(8, 8, 10), panel = Color3.fromRGB(16, 16, 19), panel2 = Color3.fromRGB(26, 26, 30), line = Color3.fromRGB(255, 255, 255), accent = Color3.fromRGB(236, 238, 243), text = Color3.fromRGB(240, 242, 246), dim = Color3.fromRGB(150, 153, 162), icon = Color3.fromRGB(192, 195, 204), button = Color3.fromRGB(30, 30, 35), buttonHi = Color3.fromRGB(48, 49, 56), cardHover = Color3.fromRGB(22, 22, 26), active = Color3.fromRGB(70, 72, 80), on = Color3.fromRGB(236, 238, 243), off = Color3.fromRGB(40, 42, 48), danger = Color3.fromRGB(255, 72, 72), blue = Color3.fromRGB(150, 170, 210), yellow = Color3.fromRGB(238, 240, 200), -- legacy names still used by the world-space labels / overlay font = Enum.Font.Gotham, fontBold = Enum.Font.GothamBold, } local FONT = Font.new("rbxasset://fonts/families/RobotoCondensed.json", Enum.FontWeight.Regular, Enum.FontStyle.Normal) local FONT_BOLD = Font.new("rbxasset://fonts/families/RobotoCondensed.json", Enum.FontWeight.Bold, Enum.FontStyle.Normal) local FONT_ITALIC = Font.new("rbxasset://fonts/families/RobotoCondensed.json", Enum.FontWeight.Regular, Enum.FontStyle.Italic) function rgb(c) if type(c) ~= "table" then return Color3.fromRGB(170, 175, 190) end local function ch(i) local v = c[i] if type(v) ~= "number" then v = c[tostring(i)] end if type(v) ~= "number" then v = 0 end return math.floor(v + 0.5) end return Color3.fromRGB(ch(1), ch(2), ch(3)) end function new(class, props, parent) local o = Instance.new(class) for k, v in pairs(props or {}) do if k ~= "Parent" then o[k] = v end end if parent then o.Parent = parent end return o end local function onClick(button, fn) -- connect every click signal a GuiButton can emit, but never run twice for -- one physical press (a mouse click fires MouseButton1Click AND Activated). local last = 0 local function fire() local now = os.clock() if now - last < 0.05 then return end last = now fn() end pcall(function() button.MouseButton1Click:Connect(fire) end) pcall(function() button.Activated:Connect(fire) end) pcall(function() button.TouchTap:Connect(fire) end) end function corner(parent, radius) return new("UICorner", { CornerRadius = UDim.new(0, radius or 10) }, parent) end function stroke(parent, colour, thickness, transparency) return new("UIStroke", { Color = colour or Color3.fromRGB(255, 255, 255), Thickness = thickness or 1, Transparency = transparency or 0.86, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, parent) end function parentGui(g) local ok = pcall(function() g.Parent = CoreGui end) if not ok or g.Parent == nil then g.Parent = LP:WaitForChild("PlayerGui") end end local function tween(obj, t, props, style, dir) local tw = TweenService:Create(obj, TweenInfo.new(t, style or Enum.EasingStyle.Quint, dir or Enum.EasingDirection.Out), props) tw:Play() return tw end -- every connection we make goes here so destroy() can tear them all down connections = {} local function bind(signal, fn) local c = signal:Connect(fn) table.insert(connections, c) return c end -- fade: group transparency animation used for open / close / tab changes ---- local fadeProps = { { "GuiObject", "BackgroundTransparency" }, { "TextLabel", "TextTransparency" }, { "TextButton", "TextTransparency" }, { "TextBox", "TextTransparency" }, { "ImageLabel", "ImageTransparency" }, { "ImageButton", "ImageTransparency" }, { "ScrollingFrame", "ScrollBarImageTransparency" }, { "UIStroke", "Transparency" }, } local originals = {} local fadeExclude = {} local function getOriginal(inst) local o = originals[inst] if o then return o end o = {} for _, pair in ipairs(fadeProps) do local okIs, is = pcall(inst.IsA, inst, pair[1]) if okIs and is then local ok, v = pcall(function() return inst[pair[2]] end) if ok and type(v) == "number" then o[pair[2]] = v end end end originals[inst] = o inst.Destroying:Connect(function() originals[inst] = nil end) return o end local function isExcluded(inst) for _, root in ipairs(fadeExclude) do if inst == root or inst:IsDescendantOf(root) then return true end end return false end local function fade(root, alpha, t) local list = root:GetDescendants() table.insert(list, root) for _, inst in ipairs(list) do if not isExcluded(inst) then local goal for prop, v in pairs(getOriginal(inst)) do goal = goal or {} goal[prop] = v + (1 - v) * (1 - alpha) end if goal then if t and t > 0 then tween(inst, t, goal, Enum.EasingStyle.Quad) else for p, v in pairs(goal) do pcall(function() inst[p] = v end) end end end end end end -- scramble text reveal ------------------------------------------------------ local glyphs = "!<>-_\\/[]{}=+*^?#@$%&" local scrambleIds = {} local function scramble(lbl, text, duration) local id = (scrambleIds[lbl] or 0) + 1 scrambleIds[lbl] = id task.spawn(function() local start, len = os.clock(), #text while lbl.Parent and scrambleIds[lbl] == id do local p = (os.clock() - start) / duration if p >= 1 then break end local revealed = math.floor(p * len) local out = table.create(len) for i = 1, len do local ch = text:sub(i, i) if i <= revealed or ch == " " then out[i] = ch else local r = math.random(1, #glyphs) out[i] = glyphs:sub(r, r) end end lbl.Text = table.concat(out) task.wait(0.03) end if scrambleIds[lbl] == id then lbl.Text = text end end) end -- hover / press feedback ---------------------------------------------------- local function fxGoal(btn, goal) local out = {} for k, v in pairs(goal) do if k ~= "ImageColor3" or btn:IsA("ImageLabel") or btn:IsA("ImageButton") then out[k] = v end end return out end local idleFx = { BackgroundColor3 = Color3.fromRGB(56, 56, 56), TextColor3 = Color3.fromRGB(193, 193, 193), ImageColor3 = Color3.fromRGB(193, 193, 193) } local hoverFx = { BackgroundColor3 = Color3.fromRGB(72, 72, 72), TextColor3 = Color3.fromRGB(255, 255, 255), ImageColor3 = Color3.fromRGB(255, 255, 255) } local activeFx = { BackgroundColor3 = Color3.fromRGB(84, 84, 84), TextColor3 = Color3.fromRGB(255, 255, 255), ImageColor3 = Color3.fromRGB(255, 255, 255) } local function buttonFx(btn, normal, hovered, isActive) normal = normal or idleFx hovered = hovered or hoverFx local scale = btn:FindFirstChildOfClass("UIScale") or new("UIScale", {}, btn) local function active() return isActive and isActive() end btn.MouseEnter:Connect(function() if not active() then tween(btn, 0.15, fxGoal(btn, hovered)) end end) btn.MouseLeave:Connect(function() tween(scale, 0.2, { Scale = 1 }) if not active() then tween(btn, 0.2, fxGoal(btn, normal)) end end) btn.MouseButton1Down:Connect(function() tween(scale, 0.08, { Scale = 0.92 }) end) btn.MouseButton1Up:Connect(function() tween(scale, 0.35, { Scale = 1 }, Enum.EasingStyle.Back) end) end -- ========================================================================== -- window -- ========================================================================== local TITLE = "PILLAR CHASE · ROLE HUB" gui = new("ScreenGui", { Name = "PillarChaseHub", ResetOnSpawn = false, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, IgnoreGuiInset = true, DisplayOrder = 10000, Enabled = false, }) parentGui(gui) win = new("Frame", { Name = "Main", AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5), Size = UDim2.fromOffset(468, 548), BackgroundColor3 = THEME.bg, BorderSizePixel = 0, Visible = false, ZIndex = 2, }, gui) win.Active = true corner(win, 16) new("UIShadow", { Transparency = 0.5, BlurRadius = UDim.new(0, 30) }, win) stroke(win, THEME.line, 2, 0.7) new("UIGradient", { Rotation = 90, Color = ColorSequence.new(THEME.panel, THEME.bg), }, win) new("UISizeConstraint", { MinSize = Vector2.new(400, 470), MaxSize = Vector2.new(680, 820) }, win) local MainScale = new("UIScale", { Scale = 1 }, win) Main = win topbar = new("Frame", { Name = "TopBar", Size = UDim2.new(1, 0, 0, 46), BackgroundColor3 = THEME.panel, BorderSizePixel = 0, ZIndex = 4, }, win) new("UICorner", { CornerRadius = UDim.new(0, 16), BottomLeftRadius = UDim.new(0, 0), BottomRightRadius = UDim.new(0, 0), }, topbar) -- hairline of white along the bottom of the header local topAccent = new("Frame", { Name = "Accent", AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0.5, 0, 1, 0), Size = UDim2.new(1, 0, 0, 2), BackgroundColor3 = THEME.line, BorderSizePixel = 0, ZIndex = 6, }, topbar) new("UIGradient", { Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0, 1), NumberSequenceKeypoint.new(0.5, 0.2), NumberSequenceKeypoint.new(1, 1), }), }, topAccent) -- logo mark local logo = new("Frame", { Name = "Logo", AnchorPoint = Vector2.new(0, 0.5), Position = UDim2.new(0, 16, 0.5, 0), Size = UDim2.fromOffset(20, 20), BackgroundColor3 = THEME.line, BorderSizePixel = 0, ZIndex = 6, }, topbar) corner(logo, 6) new("TextLabel", { BackgroundTransparency = 1, Size = UDim2.fromScale(1, 1), FontFace = FONT_BOLD, Text = "❯", TextSize = 15, TextColor3 = THEME.bg, ZIndex = 7, }, logo) local Title = new("TextLabel", { Name = "Title", BackgroundTransparency = 1, Position = UDim2.fromOffset(44, 0), Size = UDim2.new(1, -180, 1, 0), FontFace = FONT_BOLD, Text = TITLE, TextSize = 15, TextColor3 = THEME.text, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 5, }, topbar) new("UIGradient", { Rotation = 0, Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 255, 255)), ColorSequenceKeypoint.new(0.55, Color3.fromRGB(214, 217, 228)), ColorSequenceKeypoint.new(1, Color3.fromRGB(138, 143, 160)), }), }, Title) statusDot = new("TextLabel", { Name = "Dot", AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -52, 0.5, 0), Size = UDim2.fromOffset(38, 20), BackgroundTransparency = 1, FontFace = FONT_BOLD, Text = "ON", TextSize = 12, TextColor3 = THEME.on, TextXAlignment = Enum.TextXAlignment.Center, ZIndex = 5, }, topbar) local closeBtn = new("TextButton", { Name = "CloseButton", AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -12, 0.5, 0), Size = UDim2.fromOffset(30, 30), BackgroundColor3 = THEME.button, BorderSizePixel = 0, FontFace = FONT_BOLD, Text = "×", TextSize = 18, TextColor3 = THEME.icon, AutoButtonColor = false, ZIndex = 6, }, topbar) corner(closeBtn, 8) stroke(closeBtn, Color3.fromRGB(255, 255, 255), 1, 0.9) new("UIScale", {}, closeBtn) buttonFx(closeBtn, { BackgroundColor3 = THEME.button, TextColor3 = THEME.icon }, { BackgroundColor3 = Color3.fromRGB(150, 40, 40), TextColor3 = Color3.fromRGB(255, 255, 255) }) local bottomBar = new("Frame", { Name = "BottomBar", AnchorPoint = Vector2.new(0, 1), Position = UDim2.fromScale(0, 1), Size = UDim2.new(1, 0, 0, 56), BackgroundColor3 = THEME.panel, BorderSizePixel = 0, ZIndex = 4, }, win) new("UICorner", { CornerRadius = UDim.new(0, 16), TopLeftRadius = UDim.new(0, 0), TopRightRadius = UDim.new(0, 0), }, bottomBar) -- matching hairline along the top of the rail local botAccent = new("Frame", { Name = "Accent", AnchorPoint = Vector2.new(0.5, 0), Position = UDim2.new(0.5, 0, 0, 0), Size = UDim2.new(1, 0, 0, 2), BackgroundColor3 = THEME.line, BorderSizePixel = 0, ZIndex = 6, }, bottomBar) new("UIGradient", { Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0, 1), NumberSequenceKeypoint.new(0.5, 0.2), NumberSequenceKeypoint.new(1, 1), }), }, botAccent) local buttonContainer = new("Frame", { Name = "ButtonContainer", AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.42), Size = UDim2.new(1, -20, 0, 40), BackgroundTransparency = 1, ZIndex = 5, }, bottomBar) new("UIListLayout", { FillDirection = Enum.FillDirection.Horizontal, HorizontalAlignment = Enum.HorizontalAlignment.Center, VerticalAlignment = Enum.VerticalAlignment.Center, Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, }, buttonContainer) local indicator = new("Frame", { Name = "TabIndicator", AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0, 0, 1, -6), Size = UDim2.fromOffset(22, 3), BackgroundColor3 = THEME.line, BorderSizePixel = 0, ZIndex = 6, }, bottomBar) corner(indicator, 3) new("UIStroke", { Color = THEME.line, Thickness = 3, Transparency = 0.55, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, indicator) local sections = new("Frame", { Name = "Sections", Position = UDim2.fromOffset(10, 86), Size = UDim2.new(1, -20, 1, -148), BackgroundTransparency = 1, ClipsDescendants = true, ZIndex = 2, }, win) -- live round banner ----------------------------------------------------------- -- always-visible strip under the header: a state dot, the current phase -- (lobby / in round / dead / round over) and the map's name, refreshed by the -- task cadence so it never lies about where you are. local banner = new("Frame", { Name = "RoundBanner", Position = UDim2.fromOffset(10, 50), Size = UDim2.new(1, -20, 0, 30), BackgroundColor3 = THEME.panel2, BorderSizePixel = 0, ZIndex = 2, }, win) corner(banner, 8) local bannerStroke = stroke(banner, THEME.line, 1, 0.85) new("UIGradient", { Rotation = 0, Color = ColorSequence.new(Color3.fromRGB(38, 39, 45), Color3.fromRGB(24, 24, 28)), }, banner) bannerDot = new("Frame", { Name = "Dot", AnchorPoint = Vector2.new(0, 0.5), Position = UDim2.fromOffset(10, 15), Size = UDim2.fromOffset(7, 7), BackgroundColor3 = THEME.off, BorderSizePixel = 0, ZIndex = 3, }, banner) corner(bannerDot, 4) new("UIStroke", { Color = THEME.on, Thickness = 2, Transparency = 0.5, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, bannerDot) bannerText = new("TextLabel", { Name = "Phase", BackgroundTransparency = 1, Position = UDim2.fromOffset(24, 0), Size = UDim2.new(0.5, -30, 1, 0), FontFace = FONT_BOLD, Text = "Not in game.", TextSize = 12, TextColor3 = THEME.dim, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 3, }, banner) bannerMap = new("TextLabel", { Name = "Map", BackgroundTransparency = 1, AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -10, 0.5, 0), Size = UDim2.new(0.5, -30, 1, 0), FontFace = FONT, Text = "", TextSize = 11, TextColor3 = THEME.dim, TextXAlignment = Enum.TextXAlignment.Right, TextTruncate = Enum.TextTruncate.AtEnd, ZIndex = 3, }, banner) local NAV_LABEL = { Roles = "Roles", Visuals = "Visuals", HUD = "HUD", Tasks = "Tasks", Teleport = "Teleport", Extras = "Extras", Info = "Info" } local nav = {} local currentTab = nil -- toast --------------------------------------------------------------------- local ToastStroke = new("UIStroke", { Color = Color3.fromRGB(255, 255, 255), Transparency = 1, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }) local Toast = new("TextLabel", { Name = "Toast", AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0.5, 0, 1, -64), Size = UDim2.fromOffset(0, 28), AutomaticSize = Enum.AutomaticSize.X, BackgroundColor3 = THEME.panel, BackgroundTransparency = 1, BorderSizePixel = 0, FontFace = FONT, TextSize = 14, TextColor3 = Color3.fromRGB(255, 255, 255), TextTransparency = 1, Text = "", ZIndex = 30, }, win) corner(Toast, 13) new("UIPadding", { PaddingLeft = UDim.new(0, 12), PaddingRight = UDim.new(0, 12) }, Toast) ToastStroke.Parent = Toast table.insert(fadeExclude, Toast) local toastId = 0 local function toast(text, color) toastId += 1 local id = toastId Toast.TextColor3 = color or Color3.fromRGB(255, 255, 255) Toast.Position = UDim2.new(0.5, 0, 1, -56) tween(Toast, 0.3, { BackgroundTransparency = 0, TextTransparency = 0, Position = UDim2.new(0.5, 0, 1, -64) }) tween(ToastStroke, 0.3, { Transparency = 0.8 }) scramble(Toast, text, 0.25) task.delay(1.8, function() if id ~= toastId then return end tween(Toast, 0.3, { BackgroundTransparency = 1, TextTransparency = 1 }) tween(ToastStroke, 0.3, { Transparency = 1 }) end) end -- ascii backdrop ------------------------------------------------------------- -- The signature "Product Faker V2" look: an animated ASCII field breathing -- behind the window, driven by noise plus a sine shimmer and a ripple that -- follows the mouse. Three stacked labels at different transparencies keep it -- cheap (one label per brightness band instead of per-character RichText). local AsciiHolder = new("Frame", { Name = "AsciiBackdrop", AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5), BackgroundTransparency = 1, ClipsDescendants = true, Visible = false, ZIndex = 1, }, gui) table.insert(fadeExclude, AsciiHolder) local asciiCfg = { textSize = 13, padding = 110, fps = 30, ramp = " .,:;-=+*x#%@", color = Color3.fromRGB(255, 255, 255), mouse = true, } local ascii = {} local asciiAlpha = new("NumberValue", { Value = 0, Name = "AsciiAlpha" }) do local font = Enum.Font.Code local huge = Vector2.new(1e5, 1e5) local size = asciiCfg.textSize local cw = TextService:GetTextSize(string.rep("M", 40), size, font, huge).X / 40 local h1 = TextService:GetTextSize("M", size, font, huge).Y local h11 = TextService:GetTextSize(string.rep("M\n", 10) .. "M", size, font, huge).Y local ch = (h11 - h1) / 10 if cw <= 0 then cw = size * 0.6 end if ch <= 0 then ch = size end local layers = { { min = 0.1, transparency = 0.82 }, { min = 0.4, transparency = 0.55 }, { min = 0.7, transparency = 0.22 }, } local labels = {} for i in ipairs(layers) do labels[i] = new("TextLabel", { Name = "Layer" .. i, BackgroundTransparency = 1, Font = font, TextSize = size, TextColor3 = asciiCfg.color, TextTransparency = 1, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, TextWrapped = false, RichText = false, Text = "", ZIndex = 1, }, AsciiHolder) end local ramp = {} for i = 1, #asciiCfg.ramp do ramp[i] = asciiCfg.ramp:sub(i, i) end local rampLen = #ramp local cols, rows, offX, offY = 0, 0, 0, 0 local glow = {} local bufs = { {}, {}, {} } local lines = { {}, {}, {} } function ascii.rebuild(area, inner) cols = math.max(1, math.floor(area.X / cw)) rows = math.max(1, math.floor(area.Y / ch)) offX = (area.X - cols * cw) / 2 offY = (area.Y - rows * ch) / 2 for _, l in ipairs(labels) do l.Position = UDim2.fromOffset(offX, offY) l.Size = UDim2.fromOffset(cols * cw + 4, rows * ch + 4) end local cx, cy = area.X / 2, area.Y / 2 local hw, hh = inner.X / 2, inner.Y / 2 local pad = asciiCfg.padding table.clear(glow) for y = 0, rows - 1 do local qy = math.max(math.abs(offY + (y + 0.5) * ch - cy) - hh, 0) for x = 0, cols - 1 do local qx = math.max(math.abs(offX + (x + 0.5) * cw - cx) - hw, 0) local i = y * cols + x + 1 if qx == 0 and qy == 0 then glow[i] = -1 else local g = math.clamp(1 - math.sqrt(qx * qx + qy * qy) / pad, 0, 1) glow[i] = g * g * (3 - 2 * g) end end end for _, b in ipairs(bufs) do table.clear(b) end end local noise, sin, sqrt, exp, floor = math.noise, math.sin, math.sqrt, math.exp, math.floor local min1, min2, min3 = layers[1].min, layers[2].min, layers[3].min local aspect = ch / cw function ascii.render(t, alpha, mx, my) local mcx, mcy if mx then mcx, mcy = (mx - offX) / cw, (my - offY) / ch end local b1, b2, b3 = bufs[1], bufs[2], bufs[3] local l1, l2, l3 = lines[1], lines[2], lines[3] for y = 0, rows - 1 do local base = y * cols local ny = y * 0.11 - t * 0.35 local wy = math.cos(y * 0.21 - t * 0.7) for x = 0, cols - 1 do local g = glow[base + x + 1] local c1, c2, c3 = " ", " ", " " if g > 0 then local v = 0.45 + noise(x * 0.055, ny, t * 0.12) * 1.3 + sin(x * 0.12 + t * 1.1) * wy * 0.18 if mcx then local dx, dy = x - mcx, (y - mcy) * aspect local d = sqrt(dx * dx + dy * dy) v += sin(d * 0.45 - t * 7) * exp(-d * 0.09) * 0.45 end v *= g * alpha if v >= min1 then local idx = math.clamp(floor(v * rampLen) + 1, 2, rampLen) local glyph = ramp[idx] if v >= min3 then c3 = glyph elseif v >= min2 then c2 = glyph else c1 = glyph end end end b1[x + 1], b2[x + 1], b3[x + 1] = c1, c2, c3 end l1[y + 1] = table.concat(b1, "", 1, cols) l2[y + 1] = table.concat(b2, "", 1, cols) l3[y + 1] = table.concat(b3, "", 1, cols) end labels[1].Text = table.concat(l1, "\n", 1, rows) labels[2].Text = table.concat(l2, "\n", 1, rows) labels[3].Text = table.concat(l3, "\n", 1, rows) for i, layer in ipairs(layers) do labels[i].TextTransparency = 1 - (1 - layer.transparency) * alpha end end end do local acc, lastArea = 0, nil local start = os.clock() bind(RunService.RenderStepped, function(dt) AsciiHolder.Position = Main.Position local alpha = asciiAlpha.Value if alpha <= 0.001 or not Main.Visible then return end acc += dt if acc < 1 / asciiCfg.fps then return end acc = 0 local s = MainScale.Scale > 0 and MainScale.Scale or 1 local abs = Main.AbsoluteSize local inner = Vector2.new(math.floor(abs.X / s + 0.5), math.floor(abs.Y / s + 0.5)) local area = inner + Vector2.new(asciiCfg.padding * 2, asciiCfg.padding * 2) if area ~= lastArea then lastArea = area AsciiHolder.Size = UDim2.fromOffset(area.X, area.Y) ascii.rebuild(area, inner) end local mx, my if asciiCfg.mouse then local m = UserInputService:GetMouseLocation() - AsciiHolder.AbsolutePosition if m.X >= 0 and m.Y >= 0 and m.X <= area.X and m.Y <= area.Y then mx, my = m.X, m.Y end end ascii.render(os.clock() - start, alpha, mx, my) end) end -- open / close animation ---------------------------------------------------- local moveIndicator -- forward declaration function showWindow() if gui.Enabled then return end gui.Enabled = true Main.Visible = true AsciiHolder.Visible = true MainScale.Scale = 0.9 fade(Main, 0, 0) tween(MainScale, 0.4, { Scale = 1 }, Enum.EasingStyle.Back) fade(Main, 1, 0.3) tween(asciiAlpha, 0.5, { Value = 1 }, Enum.EasingStyle.Sine) scramble(Title, TITLE, 0.5) task.defer(function() if moveIndicator then moveIndicator(true) end end) end function hideWindow(cb) if not gui.Enabled then if cb then cb() end return end if closeNotice then pcall(closeNotice) end tween(MainScale, 0.18, { Scale = 0.9 }, Enum.EasingStyle.Quad, Enum.EasingDirection.In) fade(Main, 0, 0.18) tween(asciiAlpha, 0.3, { Value = 0 }, Enum.EasingStyle.Quad) task.delay(0.2, function() Main.Visible = false gui.Enabled = false AsciiHolder.Visible = false if cb then cb() end end) end onClick(closeBtn, function() hideWindow() end) -- ========================================================================== -- rows -- ========================================================================== local function section(page, text, order) local holder = new("Frame", { Size = UDim2.new(1, 0, 0, 20), BackgroundTransparency = 1, LayoutOrder = order, ZIndex = 1, }, page) local bar = new("Frame", { Name = "Accent", AnchorPoint = Vector2.new(0, 0.5), Position = UDim2.new(0, 2, 0.5, 0), Size = UDim2.fromOffset(3, 11), BackgroundColor3 = THEME.line, BorderSizePixel = 0, ZIndex = 1, }, holder) corner(bar, 2) new("UIStroke", { Color = THEME.line, Thickness = 2, Transparency = 0.7, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, bar) new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(12, 0), Size = UDim2.new(1, -12, 1, 0), FontFace = FONT_BOLD, Text = string.upper(text), TextSize = 11, TextColor3 = THEME.dim, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 1, }, holder) return holder end local function card(page, order) local c = new("Frame", { Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundColor3 = THEME.panel, BorderSizePixel = 0, LayoutOrder = order, ZIndex = 1, }, page) corner(c, 10) local cStroke = stroke(c, Color3.fromRGB(255, 255, 255), 1, 0.9) new("UIListLayout", { Padding = UDim.new(0, 2), SortOrder = Enum.SortOrder.LayoutOrder, }, c) new("UIPadding", { PaddingTop = UDim.new(0, 6), PaddingBottom = UDim.new(0, 6), PaddingLeft = UDim.new(0, 6), PaddingRight = UDim.new(0, 6), }, c) -- a little life: the panel warms and its hairline brightens on hover bind(c.MouseEnter, function() tween(c, 0.15, { BackgroundColor3 = THEME.cardHover }) tween(cStroke, 0.15, { Transparency = 0.6 }) end) bind(c.MouseLeave, function() tween(c, 0.2, { BackgroundColor3 = THEME.panel }) tween(cStroke, 0.2, { Transparency = 0.9 }) end) return c end local function toggleRow(parent, order, text, get, set) local row = new("TextButton", { Size = UDim2.new(1, 0, 0, 34), BackgroundTransparency = 1, Text = "", AutoButtonColor = false, LayoutOrder = order, ZIndex = 2, }, parent) local label = new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(6, 0), Size = UDim2.new(1, -70, 1, 0), FontFace = FONT, Text = text, TextSize = 13, TextColor3 = THEME.text, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 2, }, row) local sw = new("Frame", { Position = UDim2.new(1, -50, 0.5, -10), Size = UDim2.fromOffset(44, 20), BackgroundColor3 = THEME.off, BorderSizePixel = 0, ZIndex = 2, }, row) corner(sw, 10) stroke(sw, Color3.fromRGB(255, 255, 255), 1, 0.86) local knob = new("Frame", { Size = UDim2.fromOffset(16, 16), Position = UDim2.fromOffset(2, 2), BackgroundColor3 = Color3.fromRGB(240, 243, 250), BorderSizePixel = 0, ZIndex = 3, }, sw) corner(knob, 8) local knobGlow = new("UIStroke", { Color = THEME.bg, Thickness = 2, Transparency = 1, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, knob) local function refresh() local on = get() tween(sw, 0.22, { BackgroundColor3 = on and THEME.on or THEME.off }) knob.BackgroundColor3 = on and THEME.bg or Color3.fromRGB(240, 243, 250) knob.Position = on and UDim2.new(1, -18, 0, 2) or UDim2.fromOffset(2, 2) knobGlow.Transparency = on and 0.6 or 1 end refresh() onClick(row, function() local on = not get() set(on) -- spring the knob into place for a tactile feel refresh() local goal = on and UDim2.new(1, -18, 0, 2) or UDim2.fromOffset(2, 2) knob.Position = on and UDim2.fromOffset(2, 2) or UDim2.new(1, -18, 0, 2) tween(knob, 0.28, { Position = goal }, Enum.EasingStyle.Back) saveConfig() end) bind(row.MouseEnter, function() tween(label, 0.15, { TextColor3 = Color3.fromRGB(255, 255, 255) }) end) bind(row.MouseLeave, function() tween(label, 0.2, { TextColor3 = THEME.text }) end) return refresh end local function buttonRow(parent, order, text, callback) local b = new("TextButton", { Size = UDim2.new(1, 0, 0, 32), BackgroundColor3 = THEME.button, BorderSizePixel = 0, FontFace = FONT_BOLD, Text = text, TextSize = 13, TextColor3 = THEME.text, AutoButtonColor = false, LayoutOrder = order, ZIndex = 2, }, parent) corner(b, 9) stroke(b, Color3.fromRGB(255, 255, 255), 1, 0.9) new("UIScale", {}, b) buttonFx(b, { BackgroundColor3 = THEME.button, TextColor3 = THEME.text }, { BackgroundColor3 = THEME.buttonHi, TextColor3 = Color3.fromRGB(255, 255, 255) }) onClick(b, callback) return b end local function sliderRow(parent, order, text, min, max, get, set, fmt, snap) local row = new("Frame", { Size = UDim2.new(1, 0, 0, 46), BackgroundTransparency = 1, LayoutOrder = order, ZIndex = 2, }, parent) new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(6, 2), Size = UDim2.new(1, -12, 0, 18), FontFace = FONT, Text = text, TextSize = 13, TextColor3 = THEME.text, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 2, }, row) local valLbl = new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(6, 2), Size = UDim2.new(1, -12, 0, 18), FontFace = FONT_BOLD, Text = "", TextSize = 12, TextColor3 = THEME.accent, TextXAlignment = Enum.TextXAlignment.Right, ZIndex = 2, }, row) local track = new("Frame", { Position = UDim2.fromOffset(6, 30), Size = UDim2.new(1, -12, 0, 8), BackgroundColor3 = THEME.panel2, BorderSizePixel = 0, ZIndex = 2, }, row) corner(track, 4) stroke(track, Color3.fromRGB(255, 255, 255), 1, 0.92) local fill = new("Frame", { Size = UDim2.fromScale(0, 1), BackgroundColor3 = THEME.accent, BorderSizePixel = 0, ZIndex = 2, }, track) corner(fill, 4) new("UIStroke", { Color = THEME.accent, Thickness = 2, Transparency = 0.5, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, fill) local knob = new("Frame", { Size = UDim2.fromOffset(15, 15), AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.new(0, 0, 0.5, 0), BackgroundColor3 = THEME.bg, BorderSizePixel = 0, ZIndex = 3, }, track) corner(knob, 8) new("UIStroke", { Color = Color3.fromRGB(255, 255, 255), Thickness = 1.5, Transparency = 0.15, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, knob) local function render() local v = get() local a = math.clamp((v - min) / (max - min), 0, 1) fill.Size = UDim2.fromScale(a, 1) knob.Position = UDim2.new(a, 0, 0.5, 0) valLbl.Text = (fmt and fmt(v)) or string.format("%.2f", v) end render() local dragging = false local function apply(input) local rel = math.clamp((input.Position.X - track.AbsolutePosition.X) / math.max(1, track.AbsoluteSize.X), 0, 1) local v = min + rel * (max - min) if snap and snap > 0 then v = math.floor(v / snap + 0.5) * snap else v = math.floor(v * 100 + 0.5) / 100 end set(v) render() end local hit = new("TextButton", { Position = UDim2.fromOffset(0, 20), Size = UDim2.new(1, 0, 0, 26), BackgroundTransparency = 1, Text = "", AutoButtonColor = false, ZIndex = 4, }, row) bind(hit.InputBegan, function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true apply(input) end end) bind(hit.InputEnded, function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = false saveConfig() end end) bind(UserInputService.InputChanged, function(input) if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then apply(input) end end) return render end local PALETTE = { { 255, 45, 60 }, -- red { 255, 140, 40 }, -- orange { 255, 210, 60 }, -- amber { 96, 255, 148 }, -- green { 60, 210, 110 }, -- green { 60, 220, 220 }, -- cyan { 80, 200, 255 }, -- sky { 94, 160, 255 }, -- blue { 200, 90, 255 }, -- violet { 255, 105, 180 }, -- pink { 245, 245, 250 }, -- white { 150, 160, 175 }, -- grey } -- every colorRow registers a refresher here so its swatch / hex / sliders stay -- in sync when the value is changed from anywhere (palette, sliders, config). colorSyncers = {} local function colorRow(parent, order, text, get, set) -- read a colour channel whether the table uses numeric (1) or string ("1") -- keys, so a stale saved config can never wedge the picker. local function comp(c, i) local v = c[i] if type(v) ~= "number" then v = c[tostring(i)] end if type(v) ~= "number" then v = 0 end return math.clamp(v, 0, 255) end local refresh -- forward declaration (palette callbacks call it) local row = new("Frame", { Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1, LayoutOrder = order, ZIndex = 2, }, parent) new("UIListLayout", { Padding = UDim.new(0, 4), SortOrder = Enum.SortOrder.LayoutOrder, }, row) -- header: name + preview + quick palette local head = new("Frame", { Size = UDim2.new(1, 0, 0, 30), BackgroundTransparency = 1, LayoutOrder = 1, ZIndex = 2, }, row) new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(6, 0), Size = UDim2.new(0, 90, 1, 0), FontFace = FONT, Text = text, TextSize = 13, TextColor3 = THEME.text, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 2, }, head) local preview = new("TextButton", { Position = UDim2.fromOffset(92, 4), Size = UDim2.fromOffset(42, 22), BackgroundColor3 = rgb(get()), BorderSizePixel = 0, Text = "", AutoButtonColor = false, ZIndex = 3, }, head) corner(preview, 6) stroke(preview, Color3.fromRGB(255, 255, 255), 1, 0.45) new("UIStroke", { Color = THEME.accent, Thickness = 2, Transparency = 0.6, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, preview) for i, c in ipairs(PALETTE) do local s = new("TextButton", { Position = UDim2.fromOffset(140 + (i - 1) * 22, 5), Size = UDim2.fromOffset(18, 18), BackgroundColor3 = rgb(c), BorderSizePixel = 0, Text = "", AutoButtonColor = false, ZIndex = 3, }, head) corner(s, 5) s.MouseEnter:Connect(function() tween(s, 0.1, { Size = UDim2.fromOffset(20, 20) }) end) s.MouseLeave:Connect(function() tween(s, 0.15, { Size = UDim2.fromOffset(18, 18) }) end) onClick(s, function() set({ c[1], c[2], c[3] }) saveConfig() refresh() end) end -- expandable custom picker: red / green / blue sliders + hex readout local picker = new("Frame", { Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1, LayoutOrder = 2, Visible = false, ZIndex = 2, }, row) new("UIListLayout", { Padding = UDim.new(0, 2), SortOrder = Enum.SortOrder.LayoutOrder, }, picker) local hexRow = new("Frame", { Size = UDim2.new(1, 0, 0, 22), BackgroundTransparency = 1, LayoutOrder = 0, ZIndex = 2, }, picker) new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(6, 0), Size = UDim2.new(0, 30, 1, 0), FontFace = FONT_BOLD, Text = "HEX", TextSize = 11, TextColor3 = THEME.dim, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 2, }, hexRow) local hexBox = new("TextBox", { Position = UDim2.fromOffset(38, 1), Size = UDim2.new(0, 96, 0, 20), BackgroundColor3 = THEME.panel2, BorderSizePixel = 0, FontFace = FONT_BOLD, Text = "#FFFFFF", PlaceholderText = "#RRGGBB", TextSize = 12, TextColor3 = THEME.text, TextXAlignment = Enum.TextXAlignment.Center, ClearTextOnFocus = false, ZIndex = 3, }, hexRow) corner(hexBox, 5) stroke(hexBox, Color3.fromRGB(255, 255, 255), 1, 0.85) local bigPrev = new("Frame", { AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -2, 0, 1), Size = UDim2.fromOffset(44, 20), BackgroundColor3 = rgb(get()), BorderSizePixel = 0, ZIndex = 3, }, hexRow) corner(bigPrev, 6) stroke(bigPrev, Color3.fromRGB(255, 255, 255), 1, 0.5) local function parseHex(str) if type(str) ~= "string" then return nil end local s = str:gsub("%s", ""):gsub("^#", "") if #s ~= 6 or not s:match("^%x%x%x%x%x%x$") then return nil end return { tonumber(s:sub(1, 2), 16), tonumber(s:sub(3, 4), 16), tonumber(s:sub(5, 6), 16) } end local rR = sliderRow(picker, 1, "Red", 0, 255, function() local c = get(); return comp(c, 1) end, function(v) local c = get(); set({ v, comp(c, 2), comp(c, 3) }) end, function(v) return string.format("%d", v) end, 1) local rG = sliderRow(picker, 2, "Green", 0, 255, function() local c = get(); return comp(c, 2) end, function(v) local c = get(); set({ comp(c, 1), v, comp(c, 3) }) end, function(v) return string.format("%d", v) end, 1) local rB = sliderRow(picker, 3, "Blue", 0, 255, function() local c = get(); return comp(c, 3) end, function(v) local c = get(); set({ comp(c, 1), comp(c, 2), v }) end, function(v) return string.format("%d", v) end, 1) function refresh() local c = get() preview.BackgroundColor3 = rgb(c) bigPrev.BackgroundColor3 = rgb(c) hexBox.Text = string.format("#%02X%02X%02X", math.floor(comp(c, 1) + 0.5), math.floor(comp(c, 2) + 0.5), math.floor(comp(c, 3) + 0.5)) pcall(rR) pcall(rG) pcall(rB) end colorSyncers[#colorSyncers + 1] = refresh refresh() -- typed hex commits on Enter or when the box loses focus; bad input snaps back bind(hexBox.FocusLost, function() local c = parseHex(hexBox.Text) if c then set(c) saveConfig() end refresh() end) onClick(preview, function() picker.Visible = not picker.Visible refresh() end) return refresh end local function infoRow(parent, order, text) local row = new("Frame", { Size = UDim2.new(1, 0, 0, 22), BackgroundTransparency = 1, LayoutOrder = order, ZIndex = 1, }, parent) local lbl = new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(6, 0), Size = UDim2.new(1, -12, 1, 0), FontFace = FONT, Text = text, TextSize = 12, TextColor3 = THEME.dim, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 1, }, row) return lbl end -- ========================================================================== -- tabs (bottom rail + sliding indicator) -- ========================================================================== moveIndicator = function(instant) if not currentTab then return end local btn = nav[currentTab].btn local s = MainScale.Scale > 0 and MainScale.Scale or 1 local x = (btn.AbsolutePosition.X + btn.AbsoluteSize.X / 2 - bottomBar.AbsolutePosition.X) / s local pos = UDim2.new(0, x, 1, -6) if instant then indicator.Position = pos else tween(indicator, 0.3, { Position = pos }) end end -- staggered entrance: each section/card fades in a beat after the last so a tab -- switch feels alive instead of snapping. Runs once per switch, not per frame. local function entrance(screen) local kids = {} for _, c in ipairs(screen:GetChildren()) do if c:IsA("GuiObject") then kids[#kids + 1] = c end end table.sort(kids, function(a, b) return (a.LayoutOrder or 0) < (b.LayoutOrder or 0) end) for i, c in ipairs(kids) do fade(c, 0, 0) task.delay((i - 1) * 0.03, function() if c.Parent then fade(c, 1, 0.22) end end) end -- restore the frame chrome (scrollbar) that lives on the page itself local so = getOriginal(screen) for prop, v in pairs(so) do pcall(function() screen[prop] = v end) end end local function setTab(index, instant) if index == currentTab then return end local prev = currentTab local dir = (prev and index < prev) and -1 or 1 currentTab = index for i, tab in ipairs(nav) do local goal = (i == index) and activeFx or idleFx if instant then for k, v in pairs(fxGoal(tab.btn, goal)) do tab.btn[k] = v end else tween(tab.btn, 0.2, fxGoal(tab.btn, goal)) end end moveIndicator(instant) local screen = nav[index].page if prev then local old = nav[prev].page if instant then old.Visible = false else fade(old, 0, 0.12) tween(old, 0.2, { Position = UDim2.fromScale(-0.06 * dir, 0) }) task.delay(0.2, function() if nav[currentTab] and nav[currentTab].page ~= old then old.Visible = false end end) end end screen.Visible = true if instant then screen.Position = UDim2.new() fade(screen, 1, 0) else screen.Position = UDim2.fromScale(0.06 * dir, 0) tween(screen, 0.3, { Position = UDim2.new() }) entrance(screen) end if TP then TP.active = (nav[index].name == "Teleport") if TP.active and TP.refresh then task.defer(TP.refresh) end end end selectTab = function(idx) -- accept a tab name as well as an index, so scripts can call -- PillarChaseHub.setTab("Teleport") without knowing the ordering if type(idx) == "string" then for i, t in ipairs(nav) do if t.name == idx or string.lower(t.name) == string.lower(idx) then idx = i break end end end if type(idx) ~= "number" then return end setTab(idx, not gui.Enabled) end local function makeTab(name, order) local btn = new("TextButton", { Name = name, Size = UDim2.fromOffset(58, 38), BackgroundColor3 = THEME.button, BorderSizePixel = 0, FontFace = FONT_BOLD, Text = NAV_LABEL[name] or name, TextSize = 10, TextColor3 = THEME.icon, AutoButtonColor = false, LayoutOrder = order, ZIndex = 6, }, buttonContainer) corner(btn, 10) stroke(btn, Color3.fromRGB(255, 255, 255), 1, 0.9) new("UIScale", {}, btn) local page = new("ScrollingFrame", { Name = name .. "Page", Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, BorderSizePixel = 0, ScrollBarThickness = 3, ScrollBarImageColor3 = THEME.button, CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y, ScrollingDirection = Enum.ScrollingDirection.Y, Visible = false, ZIndex = 1, }, sections) new("UIListLayout", { Padding = UDim.new(0, 8), SortOrder = Enum.SortOrder.LayoutOrder, }, page) new("UIPadding", { PaddingTop = UDim.new(0, 2), PaddingBottom = UDim.new(0, 12), PaddingLeft = UDim.new(0, 2), PaddingRight = UDim.new(0, 2), }, page) nav[#nav + 1] = { btn = btn, page = page, name = name } local idx = #nav buttonFx(btn, idleFx, hoverFx, function() return currentTab == idx end) onClick(btn, function() setTab(idx) end) return page end bind(bottomBar:GetPropertyChangedSignal("AbsoluteSize"), function() moveIndicator(true) end) bind(bottomBar:GetPropertyChangedSignal("AbsolutePosition"), function() moveIndicator(true) end) -- ========================================================================== -- pages -- ========================================================================== -- forward declaration: the task engine (defined after the highlight engine) -- assigns to this local, so the Tasks tab button can call it. local espPage = makeTab("Roles", 1) local stylePage = makeTab("Visuals", 2) local hudPage = makeTab("HUD", 3) local tasksPage = makeTab("Tasks", 4) local tpPage = makeTab("Teleport", 5) local extrasPage = makeTab("Extras", 6) local infoPage = makeTab("Info", 7) -- Highlights section(espPage, "highlights", 1) local c1 = card(espPage, 1) toggleRow(c1, 1, "Enable highlights", function() return STATE.enabled end, function(v) STATE.enabled = v end) section(espPage, "roles to show", 2) local c2 = card(espPage, 2) for i, role in ipairs({ "Monster", "Survivor", "Sentinel", "Unknown" }) do toggleRow(c2, i, role, function() return STATE.roles[role] end, function(v) STATE.roles[role] = v end) end toggleRow(c2, 5, "Highlight yourself", function() return STATE.self end, function(v) STATE.self = v end) section(espPage, "detection range", 3) local c3 = card(espPage, 3) sliderRow(c3, 1, "Max distance", 50, 10000, function() return STATE.maxDist end, function(v) STATE.maxDist = v end, function(v) return string.format("%d studs", v) end, 50) section(espPage, "name labels", 4) local c4 = card(espPage, 4) toggleRow(c4, 1, "Show name / role labels", function() return STATE.labels end, function(v) STATE.labels = v end) toggleRow(c4, 2, "Show distance", function() return STATE.showDistance end, function(v) STATE.showDistance = v end) -- Style section(stylePage, "highlight style", 1) local s1 = card(stylePage, 1) toggleRow(s1, 1, "See through walls", function() return STATE.throughWalls end, function(v) STATE.throughWalls = v end) sliderRow(s1, 2, "Fill opacity", 0, 1, function() return STATE.fillTransparency end, function(v) STATE.fillTransparency = v end, function(v) return string.format("%d%% solid", math.floor((1 - v) * 100 + 0.5)) end) section(stylePage, "role colours", 2) local s2 = card(stylePage, 2) for i, role in ipairs({ "Monster", "Survivor", "Sentinel", "Unknown", "Self" }) do colorRow(s2, i, role, function() return STATE.colors[role] end, function(c) STATE.colors[role] = c end) end section(stylePage, "window look", 3) local s3 = card(stylePage, 3) toggleRow(s3, 1, "Unlock mouse while open", function() return STATE.freeMouse end, function(v) STATE.freeMouse = v end) local s3note = infoRow(s3, 2, "The game locks your cursor to screen centre for") local s3note2 = infoRow(s3, 3, "shiftlock. This frees it ONLY while the window is") local s3note3 = infoRow(s3, 4, "open, then restores shiftlock exactly as it was.") section(stylePage, "world lighting", 4) local s4 = card(stylePage, 4) toggleRow(s4, 1, "Fullbright", function() return STATE.fullbright end, function(v) STATE.fullbright = v end) toggleRow(s4, 2, "No fog", function() return STATE.noFog end, function(v) STATE.noFog = v end) sliderRow(s4, 3, "Field of view", 70, 120, function() return STATE.cameraFov end, function(v) STATE.cameraFov = v end, function(v) return string.format("%d deg", v) end, 1) infoRow(s4, 4, "Fullbright, no fog and zoom are local only -") infoRow(s4, 5, "they change what you see, never the map.") -- HUD section(hudPage, "hud overlay", 1) local h1 = card(hudPage, 1) toggleRow(h1, 1, "Status HUD", function() return STATE.hud end, function(v) STATE.hud = v end) toggleRow(h1, 2, "Off-screen arrows", function() return STATE.offscreen end, function(v) STATE.offscreen = v end) toggleRow(h1, 3, "Tracers", function() return STATE.tracers end, function(v) STATE.tracers = v end) toggleRow(h1, 4, "FPS counter", function() return STATE.fpsCounter end, function(v) STATE.fpsCounter = v end) toggleRow(h1, 5, "Ping (ms)", function() return STATE.pingCounter end, function(v) STATE.pingCounter = v end) section(hudPage, "hud position", 2) local hpos = card(hudPage, 2) infoRow(hpos, 1, "While the hub is open you can drag the status") infoRow(hpos, 2, "box anywhere - it is saved for next time.") buttonRow(hpos, 3, "Reset HUD position", function() STATE.hudPos = nil if hud then hud.AnchorPoint = Vector2.new(1, 0) hud.Position = UDim2.new(1, -12, 0, 32) end saveConfig() end) section(hudPage, "monster alert", 3) local h2 = card(hudPage, 2) toggleRow(h2, 1, "Monster proximity alert", function() return STATE.monsterAlert end, function(v) STATE.monsterAlert = v end) sliderRow(h2, 2, "Alert distance", 20, 400, function() return STATE.alertDist end, function(v) STATE.alertDist = v end, function(v) return string.format("%d studs", v) end, 5) -- Tasks section(tasksPage, "objectives", 1) local tk1 = card(tasksPage, 1) toggleRow(tk1, 1, "Highlight objectives", function() return STATE.shardEsp end, function(v) STATE.shardEsp = v end) toggleRow(tk1, 2, "Auto tasks (teleport + interact)", function() return STATE.autoCollect end, function(v) STATE.autoCollect = v end) sliderRow(tk1, 3, "Teleport delay", 0.05, 0.5, function() return STATE.collectDelay end, function(v) STATE.collectDelay = v end, function(v) return string.format("%.2fs / object", v) end, 0.05) sliderRow(tk1, 4, "Collect height offset", 0, 6, function() return STATE.collectHeight end, function(v) STATE.collectHeight = v end, function(v) return string.format("%d studs", v) end, 1) section(tasksPage, "objective colour", 2) local tk2 = card(tasksPage, 2) colorRow(tk2, 1, "Colour", function() return STATE.shardColor end, function(c) STATE.shardColor = c end) section(tasksPage, "other objectives", 3) local tk3 = card(tasksPage, 3) infoRow(tk3, 1, "Every 'ObjectiveInteract*' folder is scanned and") infoRow(tk3, 2, "highlighted; 'Auto tasks' walks you to each one.") infoRow(tk3, 3, "Touch objectives (shards) clear by standing on") infoRow(tk3, 4, "them. Prompt / click objectives (lockpicking,") infoRow(tk3, 5, "cleaning, keypads, valves) are server-side: firing") infoRow(tk3, 6, "them from the client does NOT advance the count,") infoRow(tk3, 7, "so the hub moves on instead of looping on one.") infoRow(tk3, 8, "Off = it never moves you anywhere.") section(tasksPage, "progress", 4) local tk4 = card(tasksPage, 4) infoStatus = infoRow(tk4, 1, "Status: -") infoMap = infoRow(tk4, 2, "Map: -") infoObj1 = infoRow(tk4, 3, "Objective 1: -") infoObj2 = infoRow(tk4, 4, "Objective 2: -") infoExit = infoRow(tk4, 5, "Exit located: -") infoShards = infoRow(tk4, 6, "Objectives left: -") section(tasksPage, "how it works", 5) local tk5 = card(tasksPage, 5) infoRow(tk5, 1, "Step 1: work out the round. If there's no map,") infoRow(tk5, 2, "you're in the lobby, or you died and are now") infoRow(tk5, 3, "spectating, nothing runs - status shows it.") infoRow(tk5, 4, "Step 2: objective ESP marks every live task") infoRow(tk5, 5, "object (shards, snow, trash, valves, keypads).") infoRow(tk5, 6, "'Auto tasks' (OFF by default) does the real") infoRow(tk5, 7, "interaction, not a fake poke: it walks you onto") infoRow(tk5, 8, "touch shards, fires live prompts, and on the") infoRow(tk5, 9, "bone/firepit task grabs each bone and carries it") infoRow(tk5, 10, "onto the fire for real. Off = never moves you.") buttonRow(tk5, 11, "Collect nearest objective now", function() collectOneShard() end) -- Teleport TP = {} section(tpPage, "where you are", 1) local tp1 = card(tpPage, 1) TP.status = infoRow(tp1, 1, "Status: -") TP.map = infoRow(tp1, 2, "Map: -") TP.obj1 = infoRow(tp1, 3, "Objective 1: -") TP.obj2 = infoRow(tp1, 4, "Objective 2: -") TP.exit = infoRow(tp1, 5, "Exit located: -") buttonRow(tp1, 6, "Rescan now", function() if TP.refresh then TP.refresh() end end) section(tpPage, "objective teleports", 2) TP.list = card(tpPage, 2) section(tpPage, "quick jump", 3) local tp3 = card(tpPage, 3) buttonRow(tp3, 1, "Nearest available task", function() if TP.goNearest then TP.goNearest() end end) buttonRow(tp3, 2, "Exit", function() if TP.goExit then TP.goExit() end end) infoRow(tp3, 3, "Moves your character onto the objective.") infoRow(tp3, 4, "Only works in a live round - nothing happens") infoRow(tp3, 5, "in the lobby or while you are dead and") infoRow(tp3, 6, "spectating.") section(tpPage, "player teleports", 4) TP.players = card(tpPage, 4) section(tpPage, "how it works", 5) local tp4 = card(tpPage, 5) infoRow(tp4, 1, "The scan works out where you are first (lobby,") infoRow(tp4, 2, "in round, dead, round over) and which map.") infoRow(tp4, 3, "Each row below is one of the game's own task") infoRow(tp4, 4, "groups, with how many objects are still live.") infoRow(tp4, 5, "This is movement + the game's own interaction,") infoRow(tp4, 6, "so the server just sees a normal player moving.") -- Extras section(extrasPage, "session tools", 1) local x1 = card(extrasPage, 1) toggleRow(x1, 1, "Anti-AFK", function() return STATE.antiAfk end, function(v) STATE.antiAfk = v end) infoRow(x1, 2, "Stops Roblox's 20-minute idle kick by tapping") infoRow(x1, 3, "the movement keys virtually. Safe and local.") section(extrasPage, "exit / escape", 2) local x2 = card(extrasPage, 2) toggleRow(x2, 1, "Highlight the exit", function() return STATE.exitEsp end, function(v) STATE.exitEsp = v end) colorRow(x2, 2, "Exit", function() return STATE.exitColor end, function(c) STATE.exitColor = c end) infoRow(x2, 3, "Draws an outline + EXIT tag over the escape") infoRow(x2, 4, "doors once the map has located them.") section(extrasPage, "crosshair", 3) local x3 = card(extrasPage, 3) toggleRow(x3, 1, "Crosshair", function() return STATE.crosshair end, function(v) STATE.crosshair = v end) colorRow(x3, 2, "Colour", function() return STATE.crosshairColor end, function(c) STATE.crosshairColor = c end) section(extrasPage, "movement", 4) local x5 = card(extrasPage, 4) toggleRow(x5, 1, "Infinite stamina (ON)", function() return STATE.infiniteStamina end, function(v) STATE.infiniteStamina = v end) infoRow(x5, 2, "Pins your stamina at max and cancels the drain") infoRow(x5, 3, "while you sprint. Client-side / cosmetic on most") infoRow(x5, 4, "servers, so it may not register on the server.") toggleRow(x5, 5, "Noclip (walk through walls)", function() return STATE.noclip end, function(v) STATE.noclip = v end) infoRow(x5, 6, "Turns off collision on your own character parts.") infoRow(x5, 7, "Moving the character is normal; the server just") infoRow(x5, 8, "sees you where you are, like any other step.") sliderRow(x5, 9, "Speed multiplier", 1, 10, function() return STATE.speedMult end, function(v) STATE.speedMult = v end, function(v) return string.format("%.1fx", v) end, 0.5) infoRow(x5, 10, "Scales walk + sprint together through the game's") infoRow(x5, 11, "own movement. 1.0x is off; slide up to 10.0x.") section(extrasPage, "hotkeys", 5) local x4 = card(extrasPage, 5) infoRow(x4, 1, "K - open / close the hub") infoRow(x4, 2, "1 - 7 - jump to a tab") infoRow(x4, 3, "H - panic: instantly hide hub + overlay") infoRow(x4, 4, "Press H again to bring everything back.") keybindBtn = buttonRow(x4, 5, "Toggle key: " .. tostring(STATE.keybind) .. " (click to rebind)", function() keybindCapturing = true keybindBtn.Text = "Press any key…" end) -- rebind capture: while armed, the next real key press becomes the toggle key. bind(UserInputService.InputBegan, function(input) if not keybindCapturing then return end if input.UserInputType ~= Enum.UserInputType.Keyboard then return end local name = input.KeyCode and input.KeyCode.Name or nil keybindCapturing = false if name and name ~= "Unknown" and Enum.KeyCode[name] then STATE.keybind = name saveConfig() keybindBtn.Text = "Toggle key: " .. name .. " (click to rebind)" toast("Toggle key set to " .. name, THEME.on) else keybindBtn.Text = "Toggle key: " .. tostring(STATE.keybind) .. " (click to rebind)" toast("That key can't be used.", THEME.danger) end end) -- Info section(infoPage, "live stats", 1) local i1 = card(infoPage, 1) infoRole = infoRow(i1, 1, "Your role: -") infoMonster = infoRow(i1, 2, "Monster: -") infoSurvivors = infoRow(i1, 3, "Survivors: -") infoTracked = infoRow(i1, 4, "Highlighted: -") infoMouse = infoRow(i1, 5, "Mouse: -") section(infoPage, "about", 2) local i2 = card(infoPage, 2) infoRow(i2, 1, "Place 121255181515820 · Pillar Chase") infoRow(i2, 2, "Highlights are local; the server never sees them.") infoRow(i2, 3, "Tabs: keys 1-7 · Window: " .. tostring(STATE.keybind) .. " · Panic: H") infoRow(i2, 4, "Anti-AFK keeps you in the round while idle.") section(infoPage, "settings", 3) local i3 = card(infoPage, 3) buttonRow(i3, 1, "Save config now", saveConfig) buttonRow(i3, 2, "Reset to defaults", function() for k, v in pairs(DEFAULTS) do STATE[k] = (type(v) == "table") and deepCopy(v) or v end saveConfig() end) selectTab(1) showWindow() end -- ========================================================================== -- overlay (HUD, arrows, tracers, alert) - kept BELOW the window -- ========================================================================== local overlay = new("ScreenGui", { Name = "PillarChaseOverlay", ResetOnSpawn = false, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, IgnoreGuiInset = true, DisplayOrder = 9999, }) parentGui(overlay) -- close notice: lives on the overlay ScreenGui (which stays enabled when the hub -- window hides itself) so closing the hub leaves the "press K" hint on screen. local closeNote = new("TextLabel", { Name = "CloseNotice", AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0.5, 0, 1, -48), Size = UDim2.fromOffset(0, 30), AutomaticSize = Enum.AutomaticSize.X, BackgroundColor3 = THEME.panel, BackgroundTransparency = 1, BorderSizePixel = 0, FontFace = FONT, TextSize = 14, TextColor3 = THEME.text, TextTransparency = 1, Text = "", Visible = false, ZIndex = 40, }, overlay) corner(closeNote, 15) new("UIPadding", { PaddingLeft = UDim.new(0, 14), PaddingRight = UDim.new(0, 14) }, closeNote) local closeNoteStroke = stroke(closeNote, THEME.line, 1, 1) local closeNoteId = 0 function closeNotice() -- grab the service here: this chunk is right at Luau's local-variable -- limit, so the file-level `TweenService` local is not reliably captured -- as an upvalue down here (it silently resolves to a nil global). local TS = game:GetService("TweenService") closeNoteId += 1 local id = closeNoteId closeNote.Text = "Press K to reopen the hub" closeNote.Visible = true closeNote.Position = UDim2.new(0.5, 0, 1, -40) TS:Create(closeNote, TweenInfo.new(0.2), { BackgroundTransparency = 0.05, TextTransparency = 0, Position = UDim2.new(0.5, 0, 1, -48) }):Play() TS:Create(closeNoteStroke, TweenInfo.new(0.2), { Transparency = 0.5 }):Play() task.delay(1.6, function() if id ~= closeNoteId then return end TS:Create(closeNote, TweenInfo.new(0.25), { BackgroundTransparency = 1, TextTransparency = 1 }):Play() TS:Create(closeNoteStroke, TweenInfo.new(0.25), { Transparency = 1 }):Play() task.delay(0.3, function() if id ~= closeNoteId then return end closeNote.Visible = false closeNote.Text = "" end) end) end -- monster alert: pulsing edge + banner local edge = new("Frame", { Name = "Edge", Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, Active = false, Visible = false, ZIndex = 1, }, overlay) local edgeStroke = new("UIStroke", { Color = THEME.danger, Thickness = 6, Transparency = 1, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, edge) local alert = new("TextLabel", { Name = "Alert", Size = UDim2.fromOffset(420, 40), Position = UDim2.new(0.5, -210, 0, 70), BackgroundColor3 = Color3.fromRGB(120, 18, 24), BackgroundTransparency = 0.15, BorderSizePixel = 0, Font = THEME.fontBold, Text = "", TextSize = 16, TextColor3 = Color3.fromRGB(255, 235, 235), Visible = false, ZIndex = 3, }, overlay) corner(alert, 9) -- tracer + arrow holders (never capture input) local tracerHolder = new("Frame", { Name = "Tracers", Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, Active = false, ZIndex = 2, }, overlay) local arrowHolder = new("Frame", { Name = "Arrows", Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, Active = false, ZIndex = 3, }, overlay) -- status hud --------------------------------------------------------------- -- A single horizontal strip docked to the right edge. The old layout stacked -- three lines vertically; this reads left-to-right like a scoreboard - role, -- then the survivor/sentinel tally, then the monster distance - so it is one -- row tall and never buries the game's own bottom-right UI. It is draggable -- and remembers where you put it, and is interactive only while the menu is -- open (so you can grab it, but it never eats your clicks mid-fight). hud = new("Frame", { Name = "HUD", AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -12, 0, 32), Size = UDim2.new(0, 0, 0, 30), AutomaticSize = Enum.AutomaticSize.X, BackgroundColor3 = THEME.bg, BackgroundTransparency = 0.22, BorderSizePixel = 0, Active = false, ZIndex = 4, }, overlay) corner(hud, 8) local hudStroke = stroke(hud, THEME.line, 1) new("UIListLayout", { FillDirection = Enum.FillDirection.Horizontal, VerticalAlignment = Enum.VerticalAlignment.Center, HorizontalAlignment = Enum.HorizontalAlignment.Left, Padding = UDim.new(0, 9), SortOrder = Enum.SortOrder.LayoutOrder, }, hud) new("UIPadding", { PaddingLeft = UDim.new(0, 11), PaddingRight = UDim.new(0, 12), PaddingTop = UDim.new(0, 6), PaddingBottom = UDim.new(0, 6), }, hud) if STATE.hudPos and type(STATE.hudPos) == "table" and type(STATE.hudPos[1]) == "number" then hud.AnchorPoint = Vector2.new(0, 0) hud.Position = UDim2.fromOffset(STATE.hudPos[1], STATE.hudPos[2]) end -- a small coloured role dot that mirrors the highlight colour of your own role local hudDot = new("Frame", { Name = "RoleDot", LayoutOrder = 1, Size = UDim2.fromOffset(8, 8), BackgroundColor3 = THEME.dim, BorderSizePixel = 0, ZIndex = 5, }, hud) corner(hudDot, 4) local function hudSep(order) local s = new("Frame", { Name = "Sep", LayoutOrder = order, Size = UDim2.fromOffset(1, 14), BackgroundColor3 = THEME.line, BorderSizePixel = 0, BackgroundTransparency = 0.4, ZIndex = 5, }, hud) return s end local function hudLabel(order, font, size, colour) return new("TextLabel", { BackgroundTransparency = 1, LayoutOrder = order, AutomaticSize = Enum.AutomaticSize.X, Size = UDim2.new(0, 0, 1, 0), Font = font, Text = "", TextSize = size, TextColor3 = colour, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Center, ZIndex = 5, }, hud) end hudRole = hudLabel(2, THEME.fontBold, 13, THEME.text) local hudSep1 = hudSep(3) hudLine1 = hudLabel(4, THEME.font, 12, THEME.dim) local hudSep2 = hudSep(5) hudLine2 = hudLabel(6, THEME.font, 12, THEME.dim) -- centre reticle local crosshair = new("Frame", { Name = "Crosshair", AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5), Size = UDim2.fromOffset(6, 6), BackgroundColor3 = Color3.fromRGB(120, 255, 160), BorderSizePixel = 0, Visible = false, ZIndex = 5, }, overlay) corner(crosshair, 3) new("UIStroke", { Color = Color3.fromRGB(0, 0, 0), Thickness = 1, Transparency = 0.4, }, crosshair) -- top-right stats (fps / ping) local statsLbl = new("TextLabel", { Name = "Stats", AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -12, 0, 10), Size = UDim2.fromOffset(180, 18), BackgroundTransparency = 1, Font = THEME.fontBold, Text = "", TextSize = 12, TextColor3 = THEME.dim, TextXAlignment = Enum.TextXAlignment.Right, ZIndex = 5, }, overlay) -- ========================================================================== -- highlight engine -- ========================================================================== local espFolder = new("Folder", { Name = "PCHub_ESP" }, Workspace) local labelHolder = new("Folder", { Name = "PCHub_Labels" }, gui) local tracked = {} --- [model] = { hl=, bb=, txt=, head= } local targets = {} --- array of {model, head, pos, role, name, dist, colour, isSelf, dead} local fx = {} --- [model] = { tracer=, arrow=, role= } local ROLE_LABEL = { Monster = "MONSTER", Survivor = "SURVIVOR", Sentinel = "SENTINEL", Unknown = "?" } local HUD_ROLE = { Monster = "Monster", Survivor = "Survivor", Sentinel = "Sentinel" } local function colourFor(role, isSelf) if isSelf then return STATE.colors.Self end return STATE.colors[role] or STATE.colors.Unknown end local function roleOf(char, plr) local t = char:GetAttribute("Type") if t == "Monster" or t == "Survivor" or t == "Sentinel" then return t end if plr and plr:GetAttribute("EndCondition") == "MONSTER" then return "Monster" end return "Unknown" end local function ensureRecord(model) local rec = tracked[model] if rec and rec.hl and rec.hl.Parent then return rec end local hl = new("Highlight", { Name = "PCHub_HL", FillColor = Color3.fromRGB(255, 255, 255), OutlineColor = Color3.fromRGB(255, 255, 255), FillTransparency = STATE.fillTransparency, OutlineTransparency = STATE.outlineTransparency, Adornee = model, }, espFolder) local bb = new("BillboardGui", { Name = "PCHub_LBL", Size = UDim2.fromOffset(190, 30), StudsOffsetWorldSpace = Vector3.new(0, 3.2, 0), AlwaysOnTop = true, MaxDistance = math.huge, LightInfluence = 0, Enabled = false, }, labelHolder) local txt = new("TextLabel", { BackgroundTransparency = 1, Size = UDim2.fromScale(1, 1), Font = Enum.Font.GothamBold, Text = "", TextSize = 14, TextColor3 = Color3.fromRGB(255, 255, 255), TextStrokeTransparency = 0.35, TextStrokeColor3 = Color3.fromRGB(0, 0, 0), }, bb) rec = { hl = hl, bb = bb, txt = txt, head = nil } tracked[model] = rec return rec end local counts = { Monster = 0, Survivor = 0, Sentinel = 0, Unknown = 0 } local trackedCount = 0 local monsterDist = nil local function stepHighlights() counts = { Monster = 0, Survivor = 0, Sentinel = 0, Unknown = 0 } trackedCount = 0 monsterDist = nil local newTargets = {} if not STATE.enabled then for model, rec in pairs(tracked) do if rec.hl then rec.hl:Destroy() end if rec.bb then rec.bb:Destroy() end tracked[model] = nil end targets = newTargets return end local cam = Workspace.CurrentCamera local origin = cam and cam.CFrame.Position or Vector3.new(0, 0, 0) local seen = {} for _, plr in ipairs(Players:GetPlayers()) do local char = plr.Character if char and char.Parent then local isSelf = (plr == LP) local role = roleOf(char, plr) counts[role] = (counts[role] or 0) + 1 local wantRole if isSelf then wantRole = STATE.self else wantRole = STATE.roles[role] == true end local hrp = char:FindFirstChild("HumanoidRootPart") or char.PrimaryPart local head = char:FindFirstChild("Head") or hrp local hum = char:FindFirstChildOfClass("Humanoid") local dead = hum ~= nil and hum.Health <= 0 local dist = hrp and (hrp.Position - origin).Magnitude or 99999 if (role == "Monster") and hrp and not isSelf then if monsterDist == nil or dist < monsterDist then monsterDist = dist end end local visible = wantRole and dist <= STATE.maxDist if visible then local rec = ensureRecord(char) seen[char] = true trackedCount = trackedCount + 1 local colour = rgb(colourFor(role, isSelf)) rec.hl.FillColor = colour rec.hl.OutlineColor = colour rec.hl.Adornee = char rec.hl.Enabled = true rec.hl.FillTransparency = STATE.fillTransparency rec.hl.OutlineTransparency = STATE.outlineTransparency rec.hl.DepthMode = STATE.throughWalls and Enum.HighlightDepthMode.AlwaysOnTop or Enum.HighlightDepthMode.Occluded if head ~= rec.head then rec.bb.Adornee = head rec.head = head end if STATE.labels then local bits = { plr.Name } if STATE.showRole then bits[#bits + 1] = "(" .. (ROLE_LABEL[role] or role) .. ")" end if STATE.showDistance then bits[#bits + 1] = string.format("%dm", math.floor(dist + 0.5)) end if isSelf then bits[#bits + 1] = "· you" end if dead then bits[#bits + 1] = "· dead" end rec.txt.Text = table.concat(bits, " ") rec.txt.TextColor3 = colour rec.bb.Enabled = true else rec.bb.Enabled = false end if hrp then newTargets[#newTargets + 1] = { model = char, head = head, pos = hrp.Position, role = role, name = plr.Name, dist = dist, colour = colour, isSelf = isSelf, dead = dead, } end end end end for model, rec in pairs(tracked) do if not seen[model] then if rec.hl then rec.hl:Destroy() end if rec.bb then rec.bb:Destroy() end tracked[model] = nil end end targets = newTargets end -- ========================================================================== -- task engine · "Collect soul shards" (Objectives1) + trash (Objectives2) -- ========================================================================== -- -- Verified live (place 121255181515820): -- * The survivor task is "Collect soul shards". workspace.Map.Objectives1 -- goes 0 -> 100 (Max). Each of the 100 Shard models under -- Map.ObjectiveInteract1 exposes a HumanoidRootPart that is "available" -- while hrp.CanTouch == true and hrp.Transparency < 1. -- * Map.ScriptHandler.SetupObjectives.ClientTaskScript owns a handle on the -- shard's Touched event; on touch it bumps Objectives1 locally and fires -- its no-argument AddStuff RemoteEvent. The server then re-validates. -- * Firing AddStuff directly is REJECTED (we tested: count unchanged), and a -- distant touch is ignored. Moving the character ONTO a shard collects it -- reliably (tested 16 -> 38). That is exactly what auto-collect does, so -- the game's own code does the collecting - we only reposition you. local shardFolder = new("Folder", { Name = "PCHub_Shards" }, Workspace) local shardRecs = {} --- [model] = Highlight local shardAvail = 0 local shardLastTotal = 0 local function mapNow() local m = Workspace:FindFirstChild("Map") if m and m.Parent == Workspace then return m end return nil end local function shardContainer(map) return map and map:FindFirstChild("ObjectiveInteract1") or nil end -- every "ObjectiveInteract*" folder in the current map. Names vary by map: -- ObjectiveInteract1 = soul shards, ObjectiveInteract = valves, -- ObjectiveInteract2 = trash / generators. local function objectiveFolders(map) local list = {} if not map then return list end for _, c in ipairs(map:GetChildren()) do if type(c.Name) == "string" and string.sub(c.Name, 1, 17) == "ObjectiveInteract" then list[#list + 1] = c end end return list end -- A "task" prompt is an enabled ProximityPrompt that advances an objective. -- Most actions are the task ("clean debris", "shovel snow", "to grab/drop" on -- an EnergyCell). The one prop that shares this shape but is NOT an objective -- is the trash map's TrashBag, whose grab/drop prompt lives inside the same -- ObjectiveInteract2 folder - counting or firing it would grab a prop instead -- of doing the task, so it is excluded by name. local function isTaskPrompt(d) if not (d:IsA("ProximityPrompt") and d.Enabled) then return false end local p = d.Parent if p and (p.Name == "TrashBag" or p:FindFirstAncestor("TrashBag")) then return false end return true end local function hasEnabledPrompt(model) for _, d in ipairs(model:GetDescendants()) do if isTaskPrompt(d) then return d end end return nil end local function isShardAvailable(hrp) return hrp ~= nil and hrp.CanTouch == true and hrp.Transparency < 1 end -- --------------------------------------------------------------------------- -- bone / firepit task ("Burn bones in firepit") -- -- This one is entirely LOCAL: the map's Bone.DragHandler clones a DragDetector -- plus an enabled ProximityPrompt onto every bone in ObjectiveInteract1, and the -- bone's own Touched handler does the objective work the instant a HELD bone -- touches the map's FireBox part (it bumps Objectives1 locally, then fires -- AddStuff). So the genuine interaction is two steps - grab the bone, carry it -- onto the fire - and that is exactly what the collector reproduces below; -- firing the prompt alone only grabs/drops it and never scores. -- --------------------------------------------------------------------------- local function findFireBox(map) local fb = map and map:FindFirstChild("FireBox", true) if fb and fb:IsA("BasePart") then return fb end return nil end local function boneDragParts(model, map) local dd = model:FindFirstChild("DragDetector") local prompt = model:FindFirstChild("ProximityPrompt") if not (dd and prompt) then return nil end local fb = findFireBox(map) if not fb then return nil end return dd, fb end -- grab a bone and carry it onto the fire; the game's Touched code completes it local function carryBoneToFire(char, model, dd, fb) if not (char and char.Parent and dd and dd.Parent and model.Parent) then return false end -- stand at the firepit: the game's own drag watch drops a held bone once it -- is 9+ studs from the holder, so we hold it right on top of the fire. pcall(function() char:PivotTo(CFrame.new(fb.Position + Vector3.new(0, 3, 0))) end) -- grab exactly the way Bone.DragHandler's prompt handler does pcall(function() dd.ReferenceInstance = char char:SetAttribute("HoldingItem", true) end) -- drive the bone onto the fire (a real touch, not a fake prompt fire) pcall(function() model.CFrame = fb.CFrame end) pcall(function() if type(firetouchinterest) == "function" then firetouchinterest(model, fb, 1) end end) return true end -- --------------------------------------------------------------------------- -- round / map awareness -- -- Step one is ALWAYS to work out whether we are actually in a live round on a -- real map, and who/what we are. Nothing (ESP, auto-collect) runs until that -- is true, so the hub never behaves as if it were mid-game when it is not. -- --------------------------------------------------------------------------- local function mapObjectName(map) local o1 = map and map:FindFirstChild("Objectives1") local n = o1 and o1:FindFirstChild("ObjName") if n and type(n.Value) == "string" and n.Value ~= "" then return n.Value end local o2 = map and map:FindFirstChild("Objectives2") local n2 = o2 and o2:FindFirstChild("ObjName") if n2 and type(n2.Value) == "string" and n2.Value ~= "" then return n2.Value end return nil end -- latest round state, refreshed on the task cadence and read by the HUD local ROUND = { inRound = false, status = "Not in game.", map = nil } -- Which phase are we in? The old version guessed from the LobbyGUI frames, but -- ResultsScreen is Visible for most of a round too, so it reported "over" while -- you were alive. These are the signals the GAME itself drives (verified from -- its own scripts): -- LobbyGUI.Lobby.Visible the pre-round menu is open -- workspace.Map.GameOver a child appears when the round ends -- workspace.Server.Timer.Value counts down while a round runs -- char.Aspects.Health.Value the REAL health (Humanoid is cosmetic) -- char.Aspects.Escaped.Value you reached the exit -- Player:GetAttribute("EndCondition") set once you are out ("You died.") local function uiPhase() local map = mapNow() if not map then return "nogame" end local pg = LP:FindFirstChildOfClass("PlayerGui") local lg = pg and pg:FindFirstChild("LobbyGUI") local lobby = lg and lg:FindFirstChild("Lobby") if lobby and lobby.Visible then return "lobby" end if map:FindFirstChild("GameOver") then return "over" end local char = LP.Character if not char or not char:FindFirstChildOfClass("Humanoid") then return "load" end local asp = char:FindFirstChild("Aspects") local esc = asp and asp:FindFirstChild("Escaped") if esc and esc.Value == true then return "escaped" end local h = asp and asp:FindFirstChild("Health") local hum = char:FindFirstChildOfClass("Humanoid") local hp = (h and h.Value) or (hum and hum.Health) or 0 if hp <= 0 then return "dead" end return "round" end -- returns (inRound, statusText, mapName) local function roundInfo() local map = mapNow() if not (map and map.Parent) then return false, "Not in game.", nil end local phase = uiPhase() if phase == "nogame" then return false, "Not in game.", nil end if phase == "lobby" then return false, "Waiting for a round...", mapObjectName(map) end if phase == "over" then return false, "Round over.", mapObjectName(map) end if phase == "escaped" then return false, "You escaped!", mapObjectName(map) end if phase == "dead" then return false, "Dead - spectating.", mapObjectName(map) end if phase == "load" then return false, "Loading into the round...", mapObjectName(map) end return true, "In round.", mapObjectName(map) end -- available = touchable root part (shards) or a live interaction prompt -- (valves, generators, snow piles, trash). local function isObjectiveAvailable(model) local hrp = model:FindFirstChild("HumanoidRootPart") if hrp and isShardAvailable(hrp) then return true end return hasEnabledPrompt(model) ~= nil end -- --------------------------------------------------------------------------- -- objective auto-collect (unified) -- -- Two collection styles exist in Pillar Chase: -- * touch shards - Map.ObjectiveInteract1, each model owns a touchable -- HumanoidRootPart; standing on it makes the game's own Touched code -- collect it (verified live). -- * prompts - snow piles (ObjectiveInteract: Twigs + a SnowPile part) -- and trash (ObjectiveInteract2: TrashPile debris). Every part exposes a -- ProximityPrompt and the game's client task script does the work when the -- prompt fires (trash: 3 fires per debris part, 12 parts per pile). -- -- The tick picks the nearest available objective across every -- ObjectiveInteract* folder and does whichever style applies. -- --------------------------------------------------------------------------- -- world position to stand on for an objective model local function objectivePosition(model) local hrp = model:FindFirstChild("HumanoidRootPart") if hrp and hrp:IsA("BasePart") then return hrp.Position end for _, name in ipairs({ "SnowPile", "TrashPile", "Trash", "Union" }) do local p = model:FindFirstChild(name, true) if p and p:IsA("BasePart") then return p.Position end end for _, d in ipairs(model:GetDescendants()) do if d:IsA("BasePart") then return d.Position end end return nil end -- fire every live prompt under a model (trash needs 3 fires per part; the tick -- re-fires until the part is gone) local function fireObjectivePrompts(model) local fired = 0 for _, d in ipairs(model:GetDescendants()) do if isTaskPrompt(d) then fired = fired + 1 local hold = d.HoldDuration pcall(function() if type(fireproximityprompt) == "function" then fireproximityprompt(d, hold + 0.05) end end) end end return fired end -- containers this session services. -- -- There is ONE master switch (STATE.autoCollect, shown as "Auto tasks"): when -- it is off this returns nothing, so the hub never teleports you anywhere on -- its own. It is off by default and is fully independent of the ESP. local function collectorFolders(map, force) local list = {} if not (STATE.autoCollect or force) then return list end for _, c in ipairs(objectiveFolders(map)) do list[#list + 1] = c end return list end -- `skip` (optional) is a set of models the auto-collector has already found it -- cannot clear (e.g. a drag-to-turn valve). They are ignored while their -- cooldown is live so the tick moves on to something it can actually finish. local function findNearestObjective(origin, force, skip) local map = mapNow() local now = os.clock() local best, bestD, bestPos for _, container in ipairs(collectorFolders(map, force)) do for _, model in ipairs(container:GetChildren()) do local skipped = skip and skip[model] and skip[model] > now if not skipped and isObjectiveAvailable(model) then local p = objectivePosition(model) if p then local d = origin and (p - origin).Magnitude or 0 if not bestD or d < bestD then best, bestD, bestPos = model, d, p end end end end end return best, bestD, bestPos end -- objective ESP + progress readout (runs on the task cadence, not per frame) local function stepShardEsp() local inRound, status, mapName = roundInfo() ROUND.inRound = inRound ROUND.status = status ROUND.map = mapName local map = mapNow() local seen = {} local avail = 0 if inRound and map then for _, container in ipairs(objectiveFolders(map)) do for _, model in ipairs(container:GetChildren()) do if isObjectiveAvailable(model) then avail = avail + 1 if STATE.shardEsp then seen[model] = true local hl = shardRecs[model] if not (hl and hl.Parent) then hl = new("Highlight", { Name = "PCHub_ShardHL", FillTransparency = 0.6, OutlineTransparency = 0.05, DepthMode = Enum.HighlightDepthMode.AlwaysOnTop, Adornee = model, }, shardFolder) shardRecs[model] = hl end local colour = rgb(STATE.shardColor) hl.FillColor = colour hl.OutlineColor = colour hl.Adornee = model hl.Enabled = true end end end end end for model, hl in pairs(shardRecs) do if not seen[model] then hl:Destroy() shardRecs[model] = nil end end shardAvail = avail -- shared readout (also consumed by the Info tab and the round banner) pcall(function() if infoStatus then infoStatus.Text = "Status: " .. status infoStatus.TextColor3 = inRound and THEME.on or THEME.dim end if infoMap then infoMap.Text = "Map: " .. (mapName or "-") end if bannerText then bannerText.Text = status bannerText.TextColor3 = inRound and THEME.on or THEME.dim end if bannerMap then bannerMap.Text = mapName or "" end if bannerDot then bannerDot.BackgroundColor3 = inRound and THEME.on or THEME.off end end) if not (inRound and map) then pcall(function() infoObj1.Text = "Objective 1: -" infoObj2.Text = "Objective 2: -" infoExit.Text = "Exit located: -" infoShards.Text = "Objectives left: -" end) return end pcall(function() local o1 = map:FindFirstChild("Objectives1") local o2 = map:FindFirstChild("Objectives2") local exit = map:FindFirstChild("ExitLocated") local n1 = o1 and o1:FindFirstChild("ObjName") local n2 = o2 and o2:FindFirstChild("ObjName") if o1 then local mx = o1:FindFirstChild("Max") infoObj1.Text = string.format("%s: %d / %d", (n1 and n1.Value ~= "" and n1.Value) or "Objective 1", o1.Value, mx and mx.Value or 100) else infoObj1.Text = "Objective 1: -" end if o2 then local mx = o2:FindFirstChild("Max") infoObj2.Text = string.format("%s: %d / %d", (n2 and n2.Value ~= "" and n2.Value) or "Objective 2", o2.Value, mx and mx.Value or 10) else infoObj2.Text = "Objective 2: -" end infoExit.Text = "Exit located: " .. (exit and tostring(exit.Value) or "-") infoShards.Text = "Objectives left: " .. avail if bannerMap then local function prog(o) if not o then return nil end local mx = o:FindFirstChild("Max") return string.format("%d/%d", o.Value, mx and mx.Value or 0) end local p1, p2 = prog(o1), prog(o2) local parts = { mapName or "" } if p1 then parts[#parts + 1] = p1 end if p2 then parts[#parts + 1] = p2 end bannerMap.Text = table.concat(parts, " · ") end end) end -- manual collection (Tasks tab button + handle.collectOneShard) function collectOneShard() local inRound = roundInfo() if not inRound then return false end local char = LP.Character local hrp = char and char:FindFirstChild("HumanoidRootPart") if not char or not hrp then return false end local model, _, pos = findNearestObjective(hrp.Position, true) if not model or not pos then return false end local dd, fb = boneDragParts(model, mapNow()) if dd and fb then return carryBoneToFire(char, model, dd, fb) end pcall(function() char:PivotTo(CFrame.new(pos + Vector3.new(0, STATE.collectHeight, 0))) end) fireObjectivePrompts(model) return true end -- --------------------------------------------------------------------------- -- Teleport tab engine -- -- Scan-first: work out where we are (lobby / in round / dead / over) and which -- map, THEN list each of the game's own task groups with the number of objects -- still live. Teleporting is just moving the character and letting the game's -- own prompt fire - the server sees an ordinary player. -- --------------------------------------------------------------------------- -- ExitLocations is a direct child of Map on most maps, but on a few it is nested -- deeper or wrapped in a folder, so find it by name recursively. A map's exit -- can be a single part, a model, or a folder holding either. local function exitLocationFolder(map) if not map then return nil end return map:FindFirstChild("ExitLocations") or map:FindFirstChild("ExitLocations", true) end local function collectExitParts(container, out, depth) depth = depth or 0 if depth > 3 or #out >= 24 then return end if container:IsA("BasePart") then out[#out + 1] = container elseif container:IsA("Model") then if container:FindFirstChildWhichIsA("BasePart", true) then out[#out + 1] = container end else for _, c in ipairs(container:GetChildren()) do collectExitParts(c, out, depth + 1) end end end -- a real exit carries the game's own escape prompt; a decorative twin (some maps -- ship two Exit models) does not, so we can use that to put the tag on the right one. local function hasEscapePrompt(inst) for _, d in ipairs(inst:GetDescendants()) do if d:IsA("ProximityPrompt") then if d.Name == "EscapePrompt" or string.find(string.lower(tostring(d.ActionText)), "escape") then return true end end end return false end -- the instances that represent the exit (for the ESP) local function exitTargets(map) local all = {} local loc = exitLocationFolder(map) if not loc then return all end if loc:IsA("BasePart") or loc:IsA("Model") then all[#all + 1] = loc else for _, c in ipairs(loc:GetChildren()) do collectExitParts(c, all) end end -- prefer the exit(s) that actually carry the escape prompt, so the outline -- and EXIT tag sit on the real escape rather than a look-alike model local real = {} for _, t in ipairs(all) do if hasEscapePrompt(t) then real[#real + 1] = t end end if #real > 0 then return real end return all end local function exitPosition(map) for _, t in ipairs(exitTargets(map)) do if t:IsA("BasePart") then return t.Position elseif t:IsA("Model") then local primary = t.PrimaryPart or t:FindFirstChildWhichIsA("BasePart", true) if primary then return primary.Position end end end return nil end local function nearestInFolder(folder, origin) local best, bestD, bestPos for _, model in ipairs(folder:GetChildren()) do if isObjectiveAvailable(model) then local p = objectivePosition(model) if p then local d = origin and (p - origin).Magnitude or 0 if not bestD or d < bestD then best, bestD, bestPos = model, d, p end end end end return best, bestD, bestPos end local function teleportTo(pos) if not pos then return false end local char = LP.Character local hrp = char and char:FindFirstChild("HumanoidRootPart") if not char or not hrp then return false end return pcall(function() char:PivotTo(CFrame.new(pos + Vector3.new(0, STATE.collectHeight, 0))) end) end local function clearGuiList(list) if not (list and list.Parent) then return end for _, c in ipairs(list:GetChildren()) do if c:IsA("GuiObject") then c:Destroy() end end end local function clearTPList() clearGuiList(TP and TP.list) clearGuiList(TP and TP.players) end local function addTPRow(order, text, enabled, action, parent) parent = parent or (TP and TP.list) if not (TP and parent) then return end local row = new("Frame", { Size = UDim2.new(1, 0, 0, 28), BackgroundTransparency = 1, LayoutOrder = order, ZIndex = 2, }, parent) new("TextLabel", { BackgroundTransparency = 1, Position = UDim2.fromOffset(6, 0), Size = UDim2.new(1, -74, 1, 0), Font = Enum.Font.Gotham, Text = text, TextSize = 12, TextColor3 = enabled and THEME.text or THEME.dim, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 2, }, row) if enabled and action then local go = new("TextButton", { AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -2, 0.5, 0), Size = UDim2.fromOffset(58, 22), BackgroundColor3 = THEME.button, BorderSizePixel = 0, Font = Enum.Font.GothamBold, Text = "GO", TextSize = 12, TextColor3 = THEME.accent, AutoButtonColor = false, ZIndex = 3, }, row) corner(go, 6) stroke(go, THEME.accent, 1, 0.8) go.MouseEnter:Connect(function() go.BackgroundColor3 = THEME.buttonHi end) go.MouseLeave:Connect(function() go.BackgroundColor3 = THEME.button end) go.MouseButton1Click:Connect(function() action() end) end return row end local function liveOrigin() local char = LP.Character local hrp = char and char:FindFirstChild("HumanoidRootPart") return hrp and hrp.Position or nil end TP = TP or {} TP.active = false TP.refresh = function() if not (TP.list and TP.list.Parent) then return end local inRound, status, mapName = roundInfo() local map = mapNow() if TP.status then TP.status.Text = "Status: " .. status TP.status.TextColor3 = inRound and THEME.on or THEME.dim end if TP.map then TP.map.Text = "Map: " .. (mapName or "-") end clearTPList() if not (inRound and map) then if TP.obj1 then TP.obj1.Text = "Objective 1: -" end if TP.obj2 then TP.obj2.Text = "Objective 2: -" end if TP.exit then TP.exit.Text = "Exit located: -" end addTPRow(1, "Not in a live round right now.", false) if TP.players then addTPRow(1, "Players list only fills in a live round.", false, nil, TP.players) end return end local o1 = map:FindFirstChild("Objectives1") local o2 = map:FindFirstChild("Objectives2") local ex = map:FindFirstChild("ExitLocated") local n1 = o1 and o1:FindFirstChild("ObjName") local n2 = o2 and o2:FindFirstChild("ObjName") if TP.obj1 then local mx = o1 and o1:FindFirstChild("Max") TP.obj1.Text = o1 and string.format("%s: %d / %d", (n1 and n1.Value ~= "" and n1.Value) or "Objective 1", o1.Value, mx and mx.Value or 100) or "Objective 1: -" end if TP.obj2 then local mx = o2 and o2:FindFirstChild("Max") TP.obj2.Text = o2 and string.format("%s: %d / %d", (n2 and n2.Value ~= "" and n2.Value) or "Objective 2", o2.Value, mx and mx.Value or 10) or "Objective 2: -" end if TP.exit then TP.exit.Text = "Exit located: " .. (ex and (ex.Value and "Yes" or "Not yet") or "-") end local order = 0 for _, folder in ipairs(objectiveFolders(map)) do order += 1 local count = 0 for _, model in ipairs(folder:GetChildren()) do if isObjectiveAvailable(model) then count += 1 end end local label = string.format("%s - %d left", folder.Name, count) if count == 0 then addTPRow(order, label .. " (cleared)", false) else addTPRow(order, label, true, function() local _, _, pos = nearestInFolder(folder, liveOrigin()) teleportTo(pos) end) end end if order == 0 then addTPRow(1, "No task groups found on this map yet.", false) order = 1 end order += 1 local epos = exitPosition(map) local located = ex ~= nil and ex.Value == true if epos then addTPRow(order, located and "Exit - teleport to the exit" or "Exit - teleport to the exit (not revealed yet)", true, function() teleportTo(epos) end) else addTPRow(order, "Exit - no exit found on this map", false) end -- players: nearest first, with role + distance, tap GO to drop onto them if TP.players then clearGuiList(TP.players) local char = LP.Character local h0 = char and char:FindFirstChild("HumanoidRootPart") local origin = h0 and h0.Position or nil local rows = {} for _, p in ipairs(Players:GetPlayers()) do if p ~= LP then local c = p.Character local hrp = c and c:FindFirstChild("HumanoidRootPart") if hrp then local d = origin and (hrp.Position - origin).Magnitude or 0 rows[#rows + 1] = { name = p.Name, pos = hrp.Position, d = d, role = roleOf(c, p) } end end end table.sort(rows, function(a, b) return a.d < b.d end) local po = 0 for _, r in ipairs(rows) do po += 1 local label = string.format("%s - %s - %d studs", r.name, tostring(r.role), math.floor(r.d + 0.5)) addTPRow(po, label, true, function() teleportTo(r.pos) end, TP.players) end if po == 0 then addTPRow(1, "No other players in the round right now.", false, nil, TP.players) end end end TP.goNearest = function() local inRound = roundInfo() if not inRound then return false end local _, _, pos = findNearestObjective(liveOrigin(), true) return teleportTo(pos) end TP.goExit = function() local map = mapNow() if not map then return false end return teleportTo(exitPosition(map)) end -- auto-collect state machine: lock onto one objective, keep touching / firing -- until it clears, pause collectDelay, then move on. A target that survives -- the whole dwell without clearing is remembered in `skip` for a while: some -- tasks (the "Turn valve" panels) are drag gestures that no prompt fire can -- satisfy, so re-locking onto them forever would stall everything else. local AC = { target = nil, started = 0, nextAt = 0, touch = false, skip = {} } local function autoCollectTick(now) if not STATE.autoCollect then AC.target = nil return end -- never act outside a live round (no map, dead, loading) local inRound = roundInfo() if not inRound then AC.target = nil return end local char = LP.Character local hrp = char and char:FindFirstChild("HumanoidRootPart") if not char or not hrp then AC.target = nil return end local map = mapNow() if AC.target then local t = AC.target if not t.Parent or not isObjectiveAvailable(t) then -- cleared (or gone): brief pause, then find the next one AC.target = nil AC.skip[t] = nil AC.nextAt = now + STATE.collectDelay pcall(function() char:SetAttribute("HoldingItem", nil) end) return end if now - AC.started > 2.5 then -- not progressing (unreachable, or a gesture we cannot fire): -- sideline it for a while so the tick can service other targets AC.skip[t] = now + 20 AC.target = nil AC.nextAt = now + STATE.collectDelay return end -- bone / firepit: the real grab-then-carry, driven by the game's touch local dd, fb = boneDragParts(t, map) if dd and fb then if roleOf(char, LP) ~= "Monster" then pcall(function() carryBoneToFire(char, t, dd, fb) end) end return end local pos = objectivePosition(t) if pos then pcall(function() char:PivotTo(CFrame.new(pos + Vector3.new(0, STATE.collectHeight, 0))) end) if not AC.touch then fireObjectivePrompts(t) end end return end if now < AC.nextAt then return end local model, _, pos = findNearestObjective(hrp.Position, nil, AC.skip) if model then AC.target = model AC.started = now local shrp = model:FindFirstChild("HumanoidRootPart") AC.touch = shrp ~= nil and isShardAvailable(shrp) if pos then pcall(function() char:PivotTo(CFrame.new(pos + Vector3.new(0, STATE.collectHeight, 0))) end) if not AC.touch then fireObjectivePrompts(model) end end else -- nothing available (or everything is on a skip cooldown); prune dead -- skip entries while we wait, then re-check shortly for m, until_ in pairs(AC.skip) do if not m.Parent or until_ < now then AC.skip[m] = nil end end AC.nextAt = now + 0.5 end end -- forward declaration: defined with the utility engine below, but called here. local stepExitEsp -- shared run flag (declared here so taskLoop below can capture it as an upvalue) local running = true local function taskLoop() local lastScan = 0 local lastTP = 0 while running do local now = os.clock() -- objective/exit scan is comparatively heavy (descendant walks), so -- run it on a slower cadence; auto-collect stays responsive. if now - lastScan >= 0.6 then lastScan = now pcall(stepShardEsp) pcall(stepExitEsp) end pcall(autoCollectTick, now) -- refresh the Teleport readout once a second, only while it is shown if TP and TP.active and TP.refresh and gui.Enabled and now - lastTP >= 1.0 then lastTP = now pcall(TP.refresh) end task.wait(0.05) end end -- ========================================================================== -- utility engine · anti-AFK, fullbright, FOV, exit ESP, stats, panic -- ========================================================================== local Lighting = game:GetService("Lighting") local VirtualUser = game:GetService("VirtualUser") local Stats = game:GetService("Stats") -- exit ESP ---------------------------------------------------------------- -- ExitLocations children are named differently per map ("ExitDoors", "Exit", -- ...), so highlight every Model / BasePart directly under ExitLocations. local exitFolder = new("Folder", { Name = "PCHub_Exit" }, Workspace) local exitRecs = {} --- [instance] = { hl=, bb=, txt= } local exitLocated = false local function clearExit() for inst, rec in pairs(exitRecs) do if rec.hl then rec.hl:Destroy() end if rec.bb then rec.bb:Destroy() end exitRecs[inst] = nil end end local function makeExitRec(inst) local hl = new("Highlight", { Name = "PCHub_ExitHL", FillTransparency = 0.6, OutlineTransparency = 0.05, DepthMode = Enum.HighlightDepthMode.AlwaysOnTop, Adornee = inst, }, exitFolder) local bb = new("BillboardGui", { Name = "PCHub_ExitLBL", Size = UDim2.fromOffset(120, 22), StudsOffsetWorldSpace = Vector3.new(0, 4, 0), AlwaysOnTop = true, MaxDistance = math.huge, LightInfluence = 0, Enabled = true, Adornee = inst, }, exitFolder) local txt = new("TextLabel", { BackgroundTransparency = 1, Size = UDim2.fromScale(1, 1), Font = Enum.Font.GothamBold, Text = "EXIT", TextSize = 14, TextColor3 = Color3.fromRGB(255, 255, 255), TextStrokeTransparency = 0.35, TextStrokeColor3 = Color3.fromRGB(0, 0, 0), }, bb) return { hl = hl, bb = bb, txt = txt } end function stepExitEsp() local map = mapNow() local flag = map and map:FindFirstChild("ExitLocated") exitLocated = flag ~= nil and flag.Value == true local want = {} if STATE.exitEsp and map then for _, inst in ipairs(exitTargets(map)) do want[inst] = true end end for inst, rec in pairs(exitRecs) do if not (want[inst] and inst.Parent) then rec.hl:Destroy() rec.bb:Destroy() exitRecs[inst] = nil end end local colour = rgb(STATE.exitColor) for inst in pairs(want) do local rec = exitRecs[inst] if not (rec and rec.hl.Parent) then rec = makeExitRec(inst) exitRecs[inst] = rec end rec.hl.Adornee = inst rec.hl.FillColor = colour rec.hl.OutlineColor = colour rec.hl.Enabled = true rec.bb.Adornee = inst rec.bb.Enabled = true rec.txt.TextColor3 = colour rec.txt.Text = exitLocated and "EXIT" or "EXIT ?" end end -- fullbright (local lighting only) ----------------------------------------- local fbSaved = nil local function applyFullbright() if STATE.fullbright then if not fbSaved then fbSaved = { Ambient = Lighting.Ambient, OutdoorAmbient = Lighting.OutdoorAmbient, Brightness = Lighting.Brightness, } end -- Brighten, but leave ClockTime and GlobalShadows ALONE. -- -- The game grades every round: on spawn it tweens the map to night -- (ClockTime 0) and desaturates it, then restores daytime in the lobby. -- Forcing ClockTime = 14 here lit the match at noon while the sky stayed -- night, and turning global shadows off flattened it: together that is -- the washed-grey, wrong-hue "inverted" look that only ever appeared -- during a match. Lifting the two ambient colours and brightness is -- enough to see by and keeps the map's own time of day and shading. Lighting.Ambient = Color3.fromRGB(150, 150, 150) Lighting.OutdoorAmbient = Color3.fromRGB(150, 150, 150) Lighting.Brightness = 3 elseif fbSaved then for k, v in pairs(fbSaved) do pcall(function() Lighting[k] = v end) end fbSaved = nil end end -- no fog (independent of fullbright) --------------------------------------- local fogSaved = nil local function applyNoFog() if STATE.noFog then local atm = Lighting:FindFirstChildOfClass("Atmosphere") if not fogSaved then fogSaved = { FogEnd = Lighting.FogEnd, FogStart = Lighting.FogStart, FogColor = Lighting.FogColor, atm = atm, atmDensity = atm and atm.Density or nil, } end Lighting.FogEnd = 100000 Lighting.FogStart = 100000 local a = fogSaved.atm if a and a.Parent then pcall(function() a.Density = 0 end) end elseif fogSaved then pcall(function() Lighting.FogEnd = fogSaved.FogEnd Lighting.FogStart = fogSaved.FogStart Lighting.FogColor = fogSaved.FogColor end) local a = fogSaved.atm if a and a.Parent and fogSaved.atmDensity then pcall(function() a.Density = fogSaved.atmDensity end) end fogSaved = nil end end -- noclip (local collision only) -------------------------------------------- local noclipParts = {} local function restoreNoclip() for p in pairs(noclipParts) do if p.Parent then pcall(function() p.CanCollide = true end) end end noclipParts = {} end local function applyNoclip() local char = LP.Character if not char then noclipParts = {} return end if not STATE.noclip then if next(noclipParts) then restoreNoclip() end return end -- prune parts from a previous life, then disable collision on this one for p in pairs(noclipParts) do if not p.Parent then noclipParts[p] = nil end end for _, p in ipairs(char:GetDescendants()) do if p:IsA("BasePart") and p.CanCollide and not noclipParts[p] then noclipParts[p] = true pcall(function() p.CanCollide = false end) end end end -- field of view ------------------------------------------------------------ local function applyFov() local cam = Workspace.CurrentCamera if cam and math.abs(cam.FieldOfView - STATE.cameraFov) > 0.1 then pcall(function() cam.FieldOfView = STATE.cameraFov end) end end -- anti-AFK ----------------------------------------------------------------- local antiAfkConn = nil local function ensureAntiAfk() if antiAfkConn or type(LP.Idled) ~= "RBXScriptSignal" then return end antiAfkConn = LP.Idled:Connect(function() if not STATE.antiAfk then return end pcall(function() VirtualUser:CaptureController() VirtualUser:ClickButton2(Vector2.new(0, 0)) end) end) end -- infinite stamina ---------------------------------------------------------- -- The LOCAL player's Aspects live on LocalPlayer.Character, not on the round -- rig under workspace.Map (those rigs are the OTHER players, named by display -- name). Stamina is a NumberValue with Max / DrainAmount / DrainSpeed / -- RegenAmount / Delay children. The game's own Control script stops a sprint -- only when Stamina.Value <= 0, so we (a) zero the drain tuning once and (b) -- re-pin the value to Max the instant the game lowers it, via a change signal -- AND the 0.4s status poll so a mapped round can never run the bar down. -- This is client-side; the server keeps its own copy, so treat it as cosmetic. local applyStamina do local staminaBound = nil local staminaConn = nil local staminaRestore = nil local function currentStamina() local char = LP.Character local aspects = char and char:FindFirstChild("Aspects") if not aspects then local map = Workspace:FindFirstChild("Map") local rig = map and (map:FindFirstChild(LP.Name) or map:FindFirstChild(LP.DisplayName)) aspects = rig and rig:FindFirstChild("Aspects") end return aspects and aspects:FindFirstChild("Stamina") or nil end -- pin the bar at Max and keep the sprint gate open local function pin(st) local m = st:FindFirstChild("Max") local cap = (m and m:IsA("ValueBase")) and m.Value or st.Value if st.Value ~= cap then pcall(function() st.Value = cap end) end local aspects = st.Parent local cs = aspects and aspects:FindFirstChild("CanSprint") if cs and cs:IsA("BoolValue") and not cs.Value then pcall(function() cs.Value = true end) end end -- put the tuning back the way the game had it local function restore(st) if not staminaRestore then return end for n, val in pairs(staminaRestore) do local v = st:FindFirstChild(n) if v and v:IsA("ValueBase") then pcall(function() v.Value = val end) end end staminaRestore = nil end applyStamina = function() local st = currentStamina() if st ~= staminaBound then if staminaConn then pcall(function() staminaConn:Disconnect() end) staminaConn = nil end if staminaBound and staminaBound.Parent then restore(staminaBound) end staminaRestore = nil staminaBound = st end if not st then return end if STATE.infiniteStamina then if not staminaRestore then staminaRestore = {} for _, n in ipairs({ "DrainAmount", "DrainSpeed" }) do local v = st:FindFirstChild(n) if v and v:IsA("ValueBase") then staminaRestore[n] = v.Value pcall(function() v.Value = 0 end) end end end pin(st) if not staminaConn then staminaConn = st:GetPropertyChangedSignal("Value"):Connect(function() if STATE.infiniteStamina and st.Parent then pin(st) end end) end else restore(st) if staminaConn then pcall(function() staminaConn:Disconnect() end) staminaConn = nil end end end end -- speed booster -------------------------------------------------------------- -- The game derives movement from the character's own Aspects: walking writes -- Humanoid.WalkSpeed = Aspects.WalkSpeed.Value and sprinting writes -- Humanoid.WalkSpeed = Aspects.SprintSpeed.Value * SpeedValue. So scaling the -- two Aspects values scales both gaits THROUGH the game's own code, with no -- per-frame movement loop fighting it. The base is captured once per character -- and put straight back the moment the slider returns to 1.0x. local applySpeed do local speedBound = nil local speedBase = nil local function restore(asp, base) for n, v in pairs(base) do local o = asp:FindFirstChild(n) if o and o:IsA("ValueBase") and v then pcall(function() o.Value = v end) end end end applySpeed = function() local char = LP.Character local asp = char and char:FindFirstChild("Aspects") local mult = math.clamp(tonumber(STATE.speedMult) or 1, 1, 10) -- character switched (respawn / role change): drop the old base if asp ~= speedBound then if speedBound and speedBound.Parent and speedBase then restore(speedBound, speedBase) end speedBound = asp speedBase = nil end if not asp then return end -- 1.0x = fully off: restore the game's own numbers, then leave it alone if mult <= 1 then if speedBase then restore(asp, speedBase) speedBase = nil local hum = asp.Parent and asp.Parent:FindFirstChildOfClass("Humanoid") local w = asp:FindFirstChild("WalkSpeed") if hum and w then pcall(function() hum.WalkSpeed = w.Value end) end end return end local walk = asp:FindFirstChild("WalkSpeed") local sprint = asp:FindFirstChild("SprintSpeed") if not speedBase then speedBase = { WalkSpeed = walk and walk.Value or nil, SprintSpeed = sprint and sprint.Value or nil, } end local tw = speedBase.WalkSpeed and speedBase.WalkSpeed * mult or nil local ts = speedBase.SprintSpeed and speedBase.SprintSpeed * mult or nil if walk and tw and math.abs(walk.Value - tw) > 0.001 then pcall(function() walk.Value = tw end) end if sprint and ts and math.abs(sprint.Value - ts) > 0.001 then pcall(function() sprint.Value = ts end) end -- make walking feel instant; still leave an active sprint to the game local hum = asp.Parent and asp.Parent:FindFirstChildOfClass("Humanoid") if hum and tw and speedBase.WalkSpeed and hum.WalkSpeed <= speedBase.WalkSpeed + 0.5 then pcall(function() hum.WalkSpeed = tw end) end end end -- fps / ping --------------------------------------------------------------- local frameCount = 0 local fps = 0 local fpsClock = os.clock() local pingMs = nil local function tickFps() frameCount = frameCount + 1 local now = os.clock() if now - fpsClock >= 1 then fps = frameCount / (now - fpsClock) frameCount = 0 fpsClock = now local ok, v = pcall(function() return Stats.Network.ServerStatsItem["Data Ping"]:GetValue() end) pingMs = ok and v or nil end end -- panic hide --------------------------------------------------------------- local panicked = false local function setPanic(hidden) panicked = hidden overlay.Enabled = not hidden if hidden then -- instant hide (no animation) so H is reliable for a screenshot Main.Visible = false gui.Enabled = false else showWindow() selectTab(1) end end -- ========================================================================== -- overlay engine (runs per frame for smooth arrows / tracers) -- ========================================================================== local function fxFor(model) local f = fx[model] if not f then local tracer = new("Frame", { Name = "Tracer", AnchorPoint = Vector2.new(0, 0.5), BackgroundColor3 = Color3.fromRGB(255, 255, 255), BorderSizePixel = 0, Visible = false, ZIndex = 2, }, tracerHolder) local arrow = new("TextLabel", { Name = "Arrow", Size = UDim2.fromOffset(26, 26), AnchorPoint = Vector2.new(0.5, 0.5), BackgroundTransparency = 1, Font = THEME.fontBold, Text = "▲", TextSize = 26, TextColor3 = Color3.fromRGB(255, 255, 255), Visible = false, ZIndex = 3, }, arrowHolder) f = { tracer = tracer, arrow = arrow } fx[model] = f end return f end local pulse = 0 local function stepOverlay(dt) local cam = Workspace.CurrentCamera if not cam then return end local vp = cam.ViewportSize local cx, cy = vp.X * 0.5, vp.Y * 0.5 local alive = {} for _, t in ipairs(targets) do alive[t.model] = true local f = fxFor(t.model) local sp, onScreen = cam:WorldToViewportPoint(t.pos) local on = onScreen and sp.Z > 0 if STATE.tracers and on and not t.isSelf then local dx, dy = sp.X - cx, vp.Y - sp.Y local len = math.sqrt(dx * dx + dy * dy) local ang = math.deg(math.atan2(dy, dx)) f.tracer.Position = UDim2.fromOffset(cx, vp.Y) f.tracer.Size = UDim2.fromOffset(len, 2) f.tracer.Rotation = ang f.tracer.BackgroundColor3 = t.colour f.tracer.BackgroundTransparency = t.dead and 0.7 or 0.25 f.tracer.Visible = true else f.tracer.Visible = false end if STATE.offscreen and not on and not t.isSelf then local dirX = sp.X - cx local dirY = sp.Y - cy if math.abs(dirX) < 0.01 and math.abs(dirY) < 0.01 then dirX = 1 end local margin = 40 local scale = math.min( (vp.X * 0.5 - margin) / math.max(1e-3, math.abs(dirX)), (vp.Y * 0.5 - margin) / math.max(1e-3, math.abs(dirY)) ) local ax = cx + dirX * scale local ay = cy + dirY * scale f.arrow.Position = UDim2.fromOffset(ax, ay) f.arrow.Rotation = math.deg(math.atan2(dirY, dirX)) + 90 f.arrow.TextColor3 = t.colour f.arrow.TextTransparency = t.dead and 0.7 or 0 f.arrow.Visible = true else f.arrow.Visible = false end end for model, f in pairs(fx) do if not alive[model] then f.tracer:Destroy() f.arrow:Destroy() fx[model] = nil end end -- HUD (horizontal right-side strip) if STATE.hud then hud.Visible = true hud.Active = gui.Enabled if not ROUND.inRound then hudDot.BackgroundColor3 = THEME.off hudRole.Text = ROUND.map and ROUND.map or "Not in game." hudRole.TextColor3 = THEME.dim hudLine1.Text = string.upper(ROUND.status or "Idle") hudLine1.TextColor3 = THEME.dim hudLine2.Text = "" else local myRole = (LP.Character and LP.Character:GetAttribute("Type")) or "-" local rc = STATE.colors and STATE.colors[myRole] local col = rc and rgb(rc) or THEME.text hudDot.BackgroundColor3 = col hudRole.Text = HUD_ROLE[myRole] or tostring(myRole) hudRole.TextColor3 = col hudLine1.Text = string.format("Survivor %d · Sentinel %d", counts.Survivor, counts.Sentinel) hudLine1.TextColor3 = THEME.dim if counts.Monster > 0 and monsterDist then hudLine2.Text = string.format("%d m", math.floor(monsterDist + 0.5)) hudLine2.TextColor3 = monsterDist <= STATE.alertDist and THEME.danger or THEME.dim else hudLine2.Text = "no monster" hudLine2.TextColor3 = THEME.dim end end else hud.Visible = false end -- monster alert local alerting = STATE.monsterAlert and monsterDist ~= nil and monsterDist <= STATE.alertDist if alerting then pulse += dt or 0.016 local a = (math.sin(pulse * 6) + 1) * 0.5 alert.Visible = true alert.Text = string.format("MONSTER NEARBY · %dm", math.floor(monsterDist + 0.5)) edge.Visible = true edgeStroke.Transparency = 0.35 + a * 0.5 edgeStroke.Thickness = 5 + a * 5 else alert.Visible = false edge.Visible = false edgeStroke.Transparency = 1 end -- crosshair if STATE.crosshair then crosshair.Visible = true crosshair.BackgroundColor3 = rgb(STATE.crosshairColor) else crosshair.Visible = false end -- fps / ping if STATE.fpsCounter or STATE.pingCounter then local parts = {} if STATE.fpsCounter then parts[#parts + 1] = string.format("%d fps", math.floor(fps + 0.5)) end if STATE.pingCounter then parts[#parts + 1] = pingMs and string.format("%d ms", math.floor(pingMs + 0.5)) or "-- ms" end statsLbl.Text = table.concat(parts, " ") statsLbl.Visible = true else statsLbl.Visible = false end end -- ========================================================================== -- window chrome -- ========================================================================== local function makeDraggable(frame, handle) local dragging, dragStart, startPos handle.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true dragStart = input.Position startPos = frame.Position end end) handle.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = false end end) UserInputService.InputChanged:Connect(function(input) if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then local delta = input.Position - dragStart frame.Position = UDim2.new( startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y ) end end) end makeDraggable(win, topbar) -- HUD drag: only while the menu is open, clamped to the screen, position saved -- into the config so it comes back where you left it. local hudDragging, hudDragStart, hudStartPos = false, nil, nil local function hudSave() STATE.hudPos = { hud.Position.X.Offset, hud.Position.Y.Offset } saveConfig() end hud.InputBegan:Connect(function(input) if not gui.Enabled then return end if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then hudDragging = true hudDragStart = input.Position -- normalise to a plain top-left offset so clamping is simple local ap = hud.AbsolutePosition hud.AnchorPoint = Vector2.new(0, 0) hud.Position = UDim2.fromOffset(ap.X, ap.Y) hudStartPos = hud.Position end end) UserInputService.InputChanged:Connect(function(input) if not hudDragging then return end if input.UserInputType ~= Enum.UserInputType.MouseMovement and input.UserInputType ~= Enum.UserInputType.Touch then return end local delta = input.Position - hudDragStart local screen = overlay.AbsoluteSize local maxX = math.max(0, screen.X - hud.AbsoluteSize.X) local maxY = math.max(0, screen.Y - hud.AbsoluteSize.Y) local x = math.clamp(hudStartPos.X.Offset + delta.X, 0, maxX) local y = math.clamp(hudStartPos.Y.Offset + delta.Y, 0, maxY) hud.Position = UDim2.fromOffset(x, y) end) UserInputService.InputEnded:Connect(function(input) if hudDragging and (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) then hudDragging = false hudSave() end end) -- the keybind (default K) toggles the whole window. `panicked` (H) always -- wins: if we are hidden by panic, the toggle brings everything back first. local function toggleWindow() if panicked then setPanic(false) return end if gui.Enabled then hideWindow() else showWindow() end end -- ========================================================================== -- loops -- ========================================================================== ensureAntiAfk() local function highlightLoop() while running do local ok = pcall(stepHighlights) pcall(function() if not ROUND.inRound then infoRole.Text = "Status: " .. (ROUND.status or "Not in game.") infoMonster.Text = ROUND.map and ("Map: " .. ROUND.map) or "Map: -" infoSurvivors.Text = "Survivors: -" infoTracked.Text = "Highlighted: -" return end local lr = LP.Character and LP.Character:GetAttribute("Type") infoRole.Text = "Your role: " .. tostring(lr or "-") infoMonster.Text = "Monster: " .. (counts.Monster > 0 and (monsterDist and string.format("%dm", math.floor(monsterDist + 0.5)) or "present") or "none") infoSurvivors.Text = "Survivors: " .. tostring(counts.Survivor) infoTracked.Text = "Highlighted: " .. tostring(trackedCount) end) task.wait(ok and 0.12 or 0.4) end end task.spawn(highlightLoop) task.spawn(taskLoop) RunService.RenderStepped:Connect(function(dt) if running then pcall(tickFps) pcall(applyFov) pcall(applyNoclip) pcall(applySpeed) pcall(stepOverlay, dt) end end) -- free the cursor only while the hub window is open ------------------------- -- The game's shiftlock does NOT just set UserInputService.MouseBehavior: it -- goes through PlayerModule.CameraModule.CameraUtils, which remembers the -- value it displaced and puts it back later. Writing MouseBehavior directly -- (as this used to) beat the game's own bookkeeping out of step, which is what -- left shiftlock "disturbed" after closing the hub. -- -- So we push our unlock through that SAME utility instead of around it: -- open -> CameraUtils.setMouseBehaviorOverride(Default) -- close -> CameraUtils.restoreMouseBehavior() (pops our override, then we -- settle on exactly the value the game had when we opened). -- Shiftlock's own state therefore stays consistent and keeps working. local cameraUtils do local ok, mod = pcall(function() local pm = LP.PlayerScripts:FindFirstChild("PlayerModule") local cm = pm and pm:FindFirstChild("CameraModule") local cu = cm and cm:FindFirstChild("CameraUtils") return cu and require(cu) end) if ok and type(mod) == "table" and type(mod.setMouseBehaviorOverride) == "function" then cameraUtils = mod end end local mouseHeld = false local savedMouseBehavior = nil local savedMouseIcon = nil local mouseGuard = nil local iconGuard = nil -- ask for a free cursor through the game's own camera utility when we can, so -- its override bookkeeping stays correct; fall back to a plain write if the -- module is not there (lobby / not loaded yet). local function wantFreeCursor() if cameraUtils then local ok = pcall(cameraUtils.setMouseBehaviorOverride, Enum.MouseBehavior.Default) if ok then return end end pcall(function() UserInputService.MouseBehavior = Enum.MouseBehavior.Default end) end local function holdMouse() if mouseHeld then return end mouseHeld = true savedMouseBehavior = UserInputService.MouseBehavior savedMouseIcon = UserInputService.MouseIconEnabled wantFreeCursor() pcall(function() UserInputService.MouseIconEnabled = true end) if mouseGuard then mouseGuard:Disconnect() end mouseGuard = UserInputService:GetPropertyChangedSignal("MouseBehavior"):Connect(function() if mouseHeld and UserInputService.MouseBehavior ~= Enum.MouseBehavior.Default then wantFreeCursor() end end) if iconGuard then iconGuard:Disconnect() end iconGuard = UserInputService:GetPropertyChangedSignal("MouseIconEnabled"):Connect(function() if mouseHeld and not UserInputService.MouseIconEnabled then pcall(function() UserInputService.MouseIconEnabled = true end) end end) end local function giveMouseBack() if not mouseHeld then return end mouseHeld = false if mouseGuard then mouseGuard:Disconnect() mouseGuard = nil end if iconGuard then iconGuard:Disconnect() iconGuard = nil end -- pop our override the game's way first (keeps its state consistent)... if cameraUtils then pcall(cameraUtils.restoreMouseBehavior) end -- ...then make sure we end on exactly the behaviour the game had before. if savedMouseBehavior ~= nil and UserInputService.MouseBehavior ~= savedMouseBehavior then pcall(function() UserInputService.MouseBehavior = savedMouseBehavior end) end if savedMouseIcon ~= nil then pcall(function() UserInputService.MouseIconEnabled = savedMouseIcon end) end savedMouseBehavior = nil savedMouseIcon = nil end local function stepMouse() local want = gui.Enabled and STATE.freeMouse and not panicked if want then holdMouse() if UserInputService.MouseBehavior ~= Enum.MouseBehavior.Default then wantFreeCursor() end if not UserInputService.MouseIconEnabled then UserInputService.MouseIconEnabled = true end else giveMouseBack() end end RunService.RenderStepped:Connect(stepMouse) local function statusLoop() while running do pcall(applyFullbright) pcall(applyNoFog) pcall(applyStamina) pcall(applySpeed) pcall(function() for _, fn in ipairs(colorSyncers) do fn() end end) pcall(function() statusDot.Text = STATE.enabled and "ON" or "OFF" statusDot.TextColor3 = STATE.enabled and THEME.on or THEME.off infoMouse.Text = "Mouse: " .. tostring(UserInputService.MouseBehavior):gsub("Enum.MouseBehavior.", "") end) task.wait(0.4) end end task.spawn(statusLoop) -- keybinds local TAB_KEYS = { [Enum.KeyCode.One] = 1, [Enum.KeyCode.Two] = 2, [Enum.KeyCode.Three] = 3, [Enum.KeyCode.Four] = 4, [Enum.KeyCode.Five] = 5, [Enum.KeyCode.Six] = 6, [Enum.KeyCode.Seven] = 7, } UserInputService.InputBegan:Connect(function(input, gameProcessed) if gameProcessed or keybindCapturing then return end if input.UserInputType ~= Enum.UserInputType.Keyboard and input.UserInputType ~= Enum.UserInputType.Gamepad1 then return end local kb = STATE.keybind and Enum.KeyCode[STATE.keybind] if kb and input.KeyCode == kb then toggleWindow() return end if input.KeyCode == Enum.KeyCode.H then setPanic(not panicked) return end local tab = TAB_KEYS[input.KeyCode] if tab and gui.Enabled and not panicked then selectTab(tab) end end) -- ========================================================================== -- session handle -- ========================================================================== local handle = {} handle.gui = gui handle.overlay = overlay handle.cameraUtils = cameraUtils handle.state = STATE handle.setTab = selectTab handle.toggleWindow = toggleWindow handle.collectOneShard = collectOneShard handle.autoTasks = function(on) STATE.autoCollect = on ~= false saveConfig() return STATE.autoCollect end handle.autoCollect = handle.autoTasks handle.getCounts = function() return counts, trackedCount, monsterDist end handle.roundInfo = roundInfo handle.uiPhase = uiPhase handle.setPanic = setPanic handle.closeNotice = function() local ok, err = pcall(closeNotice) return ok, tostring(err) end handle.closeNote = closeNote -- internal engines, exposed for testing / scripting handle.runShardEsp = stepShardEsp handle.runHighlights = stepHighlights handle.runExitEsp = stepExitEsp handle.roleOf = roleOf handle.teleportNearest = function() return TP and TP.goNearest and TP.goNearest() end handle.teleportExit = function() return TP and TP.goExit and TP.goExit() end handle.tp = TP handle.mapNow = mapNow handle.antiAfk = function(on) STATE.antiAfk = on ~= false saveConfig() ensureAntiAfk() return STATE.antiAfk end handle.noclip = function(on) STATE.noclip = on ~= false if not STATE.noclip then restoreNoclip() end saveConfig() return STATE.noclip end handle.noFog = function(on) STATE.noFog = on ~= false applyNoFog() saveConfig() return STATE.noFog end handle.infiniteStamina = function(on) STATE.infiniteStamina = on ~= false applyStamina() saveConfig() return STATE.infiniteStamina end handle.speed = function(mult) STATE.speedMult = math.clamp(tonumber(mult) or 1, 1, 10) applySpeed() saveConfig() return STATE.speedMult end handle.setFreeMouse = function(on) STATE.freeMouse = on ~= false if not STATE.freeMouse then giveMouseBack() end saveConfig() return STATE.freeMouse end handle.releaseMouse = giveMouseBack handle.destroy = function() running = false pcall(giveMouseBack) if antiAfkConn then pcall(function() antiAfkConn:Disconnect() end) antiAfkConn = nil end pcall(function() if fbSaved then for k, v in pairs(fbSaved) do Lighting[k] = v end end end) pcall(restoreNoclip) pcall(function() STATE.infiniteStamina = false applyStamina() end) pcall(function() STATE.speedMult = 1 applySpeed() end) pcall(function() STATE.noFog = false applyNoFog() end) pcall(function() for _, rec in pairs(tracked) do if rec.hl then rec.hl:Destroy() end if rec.bb then rec.bb:Destroy() end end end) pcall(function() for _, f in pairs(fx) do f.tracer:Destroy() f.arrow:Destroy() end end) pcall(function() clearExit() end) pcall(function() espFolder:Destroy() end) pcall(function() shardFolder:Destroy() end) pcall(function() exitFolder:Destroy() end) pcall(function() gui:Destroy() end) pcall(function() overlay:Destroy() end) pcall(function() for _, c in ipairs(connections) do c:Disconnect() end end) end handle.cleanup = handle.destroy genv.PillarChaseHub = handle print("[PCHub] v2 loaded. Monster=red, Survivor=green, Sentinel=amber.") print(string.format("[PCHub] %s = window, keys 1-7 = tabs, H = panic hide. Cursor frees while the window is open and is handed straight back on close. %d players seen.", tostring(STATE.keybind), #Players:GetPlayers())) print("[PCHub] Extras: infinite stamina, speed booster, anti-AFK, fullbright, no fog, noclip, FOV zoom, exit ESP, crosshair, fps/ping.") print("[PCHub] Tasks: round-aware. Objective ESP on all maps; 'Auto tasks' is one OFF-by-default toggle. Lobby/dead = idle.")