Run it
Copy the script and paste it into your executor.
.PlaceId ~=
prev = ().PillarChaseHub
prev (prev.destroy) ==
(prev.destroy)
Players = :()
UserInputService = :()
RunService = :()
CoreGui = :()
Workspace =
LP = Players.LocalPlayer
genv = (() == ())
previous = genv.PillarChaseHub
(previous) == (previous.destroy) ==
(previous.destroy)
DEFAULTS = {
enabled = ,
throughWalls = ,
fillTransparency = ,
outlineTransparency = ,
labels = ,
showRole = ,
showDistance = ,
maxDist = ,
self = ,
freeMouse = ,
hud = ,
tracers = ,
offscreen = ,
monsterAlert = ,
alertDist = ,
shardEsp = ,
shardColor = { , , },
autoCollect = ,
collectDelay = ,
collectHeight = ,
infiniteStamina = ,
antiAfk = ,
fullbright = ,
noFog = ,
noclip = ,
speedMult = ,
cameraFov = ,
exitEsp = ,
exitColor = { , , },
crosshair = ,
crosshairColor = { , , },
fpsCounter = ,
pingCounter = ,
roles = {
Monster = ,
Survivor = ,
Sentinel = ,
Unknown = ,
},
colors = {
Monster = { , , },
Survivor = { , , },
Sentinel = { , , },
Unknown = { , , },
Self = { , , },
},
keybind = ,
}
STATE = {}
k, v (DEFAULTS)
(v) ==
t = {}
k2, v2 (v)
t[k2] = ((v2) == ) { v2[], v2[], v2[] } v2
STATE[k] = t
STATE[k] = v
CONFIG_FILE =
(t)
out = {}
k, v (t)
out[k] = ((v) == ) (v) v
out
()
(()
(writefile) ~=
(v)
tv = (v)
tv == tv ==
(v)
tv ==
.(, v)
tv ==
parts = {}
k, vv (v)
key
(k) ==
key = .(, k)
key = .(, (k))
parts[#parts + ] = key .. .. (vv)
.. .(parts, ) ..
(CONFIG_FILE, .. (STATE))
)
()
(()
(isfile) ~= (readfile) ~=
(CONFIG_FILE)
fn = ((CONFIG_FILE))
fn
saved = ()
(saved) ~=
k, v (saved)
(v) == (STATE[k]) ==
k2, v2 (v)
key = k2
(k2) ==
num = (k2)
num (num) == k2
key = num
STATE[k][key] = v2
STATE[k] = v
)
()
THEME, rgb, new, corner, stroke, parentGui
gui, win, Main, topbar, statusDot, selectTab, showWindow, hideWindow, closeNotice
connections, colorSyncers, collectOneShard, hud
infoStatus, infoMap, infoObj1, infoObj2, infoExit, infoShards
infoRole, infoMonster, infoSurvivors, infoTracked, infoMouse
keybindBtn, keybindCapturing, bannerText, bannerMap, bannerDot
TP
TweenService = :()
TextService = :()
THEME = {
bg = .(, , ),
panel = .(, , ),
panel2 = .(, , ),
line = .(, , ),
accent = .(, , ),
text = .(, , ),
dim = .(, , ),
icon = .(, , ),
button = .(, , ),
buttonHi = .(, , ),
cardHover = .(, , ),
active = .(, , ),
on = .(, , ),
off = .(, , ),
danger = .(, , ),
blue = .(, , ),
yellow = .(, , ),
font = .Font.Gotham,
fontBold = .Font.GothamBold,
}
FONT = Font.(, .FontWeight.Regular, .FontStyle.Normal)
FONT_BOLD = Font.(, .FontWeight.Bold, .FontStyle.Normal)
FONT_ITALIC = Font.(, .FontWeight.Regular, .FontStyle.Italic)
(c)
(c) ~=
.(, , )
(i)
v = c[i]
(v) ~=
v = c[(i)]
(v) ~=
v =
.(v + )
.((), (), ())
(class, props, parent)
o = .(class)
k, v (props {})
k ~=
o[k] = v
parent
o.Parent = parent
o
(button, fn)
last =
()
now = .()
now - last <
last = now
()
(() button.MouseButton1Click:(fire) )
(() button.Activated:(fire) )
(() button.TouchTap:(fire) )
(parent, radius)
(, { CornerRadius = .(, radius ) }, parent)
(parent, colour, thickness, transparency)
(, {
Color = colour .(, , ),
Thickness = thickness ,
Transparency = transparency ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, parent)
(g)
ok = (()
g.Parent = CoreGui
)
ok g.Parent ==
g.Parent = LP:()
(obj, t, props, style, dir)
tw = TweenService:(obj, TweenInfo.(t, style .EasingStyle.Quint, dir .EasingDirection.Out), props)
tw:()
tw
connections = {}
(signal, fn)
c = signal:(fn)
.(connections, c)
c
fadeProps = {
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
}
originals = {}
fadeExclude = {}
(inst)
o = originals[inst]
o
o
o = {}
_, pair (fadeProps)
okIs, is = (inst.IsA, inst, pair[])
okIs is
ok, v = (()
inst[pair[]]
)
ok (v) ==
o[pair[]] = v
originals[inst] = o
inst.Destroying:(()
originals[inst] =
)
o
(inst)
_, root (fadeExclude)
inst == root inst:(root)
(root, alpha, t)
list = root:()
.(list, root)
_, inst (list)
(inst)
goal
prop, v ((inst))
goal = goal {}
goal[prop] = v + ( - v) * ( - alpha)
goal
t t >
(inst, t, goal, .EasingStyle.Quad)
p, v (goal)
(()
inst[p] = v
)
glyphs =
scrambleIds = {}
(lbl, text, duration)
id = (scrambleIds[lbl] ) +
scrambleIds[lbl] = id
.(()
start, len = .(), #text
lbl.Parent scrambleIds[lbl] == id
p = (.() - start) / duration
p >=
revealed = .(p * len)
out = .(len)
i = , len
ch = text:(i, i)
i <= revealed ch ==
out[i] = ch
r = .(, #glyphs)
out[i] = glyphs:(r, r)
lbl.Text = .(out)
.()
scrambleIds[lbl] == id
lbl.Text = text
)
(btn, goal)
out = {}
k, v (goal)
k ~= btn:() btn:()
out[k] = v
out
idleFx = { BackgroundColor3 = .(, , ), TextColor3 = .(, , ), ImageColor3 = .(, , ) }
hoverFx = { BackgroundColor3 = .(, , ), TextColor3 = .(, , ), ImageColor3 = .(, , ) }
activeFx = { BackgroundColor3 = .(, , ), TextColor3 = .(, , ), ImageColor3 = .(, , ) }
(btn, normal, hovered, isActive)
normal = normal idleFx
hovered = hovered hoverFx
scale = btn:() (, {}, btn)
()
isActive ()
btn.MouseEnter:(()
()
(btn, , (btn, hovered))
)
btn.MouseLeave:(()
(scale, , { Scale = })
()
(btn, , (btn, normal))
)
btn.MouseButton1Down:(()
(scale, , { Scale = })
)
btn.MouseButton1Up:(()
(scale, , { Scale = }, .EasingStyle.Back)
)
TITLE =
gui = (, {
Name = ,
ResetOnSpawn = ,
ZIndexBehavior = .ZIndexBehavior.Sibling,
IgnoreGuiInset = ,
DisplayOrder = ,
Enabled = ,
})
(gui)
win = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, ),
Size = .(, ),
BackgroundColor3 = THEME.bg,
BorderSizePixel = ,
Visible = ,
ZIndex = ,
}, gui)
win.Active =
(win, )
(, { Transparency = , BlurRadius = .(, ) }, win)
(win, THEME.line, , )
(, {
Rotation = ,
Color = ColorSequence.(THEME.panel, THEME.bg),
}, win)
(, { MinSize = .(, ), MaxSize = .(, ) }, win)
MainScale = (, { Scale = }, win)
Main = win
topbar = (, {
Name = ,
Size = .(, , , ),
BackgroundColor3 = THEME.panel,
BorderSizePixel = ,
ZIndex = ,
}, win)
(, {
CornerRadius = .(, ),
BottomLeftRadius = .(, ),
BottomRightRadius = .(, ),
}, topbar)
topAccent = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, , , ),
Size = .(, , , ),
BackgroundColor3 = THEME.line,
BorderSizePixel = ,
ZIndex = ,
}, topbar)
(, {
Transparency = NumberSequence.({
NumberSequenceKeypoint.(, ),
NumberSequenceKeypoint.(, ),
NumberSequenceKeypoint.(, ),
}),
}, topAccent)
logo = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, , , ),
Size = .(, ),
BackgroundColor3 = THEME.line,
BorderSizePixel = ,
ZIndex = ,
}, topbar)
(logo, )
(, {
BackgroundTransparency = ,
Size = .(, ),
FontFace = FONT_BOLD,
Text = ,
TextSize = ,
TextColor3 = THEME.bg,
ZIndex = ,
}, logo)
Title = (, {
Name = ,
BackgroundTransparency = ,
Position = .(, ),
Size = .(, -, , ),
FontFace = FONT_BOLD,
Text = TITLE,
TextSize = ,
TextColor3 = THEME.text,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, topbar)
(, {
Rotation = ,
Color = ColorSequence.({
ColorSequenceKeypoint.(, .(, , )),
ColorSequenceKeypoint.(, .(, , )),
ColorSequenceKeypoint.(, .(, , )),
}),
}, Title)
statusDot = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, -, , ),
Size = .(, ),
BackgroundTransparency = ,
FontFace = FONT_BOLD,
Text = ,
TextSize = ,
TextColor3 = THEME.on,
TextXAlignment = .TextXAlignment.Center,
ZIndex = ,
}, topbar)
closeBtn = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, -, , ),
Size = .(, ),
BackgroundColor3 = THEME.button,
BorderSizePixel = ,
FontFace = FONT_BOLD,
Text = ,
TextSize = ,
TextColor3 = THEME.icon,
AutoButtonColor = ,
ZIndex = ,
}, topbar)
(closeBtn, )
(closeBtn, .(, , ), , )
(, {}, closeBtn)
(closeBtn,
{ BackgroundColor3 = THEME.button, TextColor3 = THEME.icon },
{ BackgroundColor3 = .(, , ), TextColor3 = .(, , ) })
bottomBar = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, ),
Size = .(, , , ),
BackgroundColor3 = THEME.panel,
BorderSizePixel = ,
ZIndex = ,
}, win)
(, {
CornerRadius = .(, ),
TopLeftRadius = .(, ),
TopRightRadius = .(, ),
}, bottomBar)
botAccent = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, , , ),
Size = .(, , , ),
BackgroundColor3 = THEME.line,
BorderSizePixel = ,
ZIndex = ,
}, bottomBar)
(, {
Transparency = NumberSequence.({
NumberSequenceKeypoint.(, ),
NumberSequenceKeypoint.(, ),
NumberSequenceKeypoint.(, ),
}),
}, botAccent)
buttonContainer = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, ),
Size = .(, -, , ),
BackgroundTransparency = ,
ZIndex = ,
}, bottomBar)
(, {
FillDirection = .FillDirection.Horizontal,
HorizontalAlignment = .HorizontalAlignment.Center,
VerticalAlignment = .VerticalAlignment.Center,
Padding = .(, ),
SortOrder = .SortOrder.LayoutOrder,
}, buttonContainer)
indicator = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, , , -),
Size = .(, ),
BackgroundColor3 = THEME.line,
BorderSizePixel = ,
ZIndex = ,
}, bottomBar)
(indicator, )
(, {
Color = THEME.line,
Thickness = ,
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, indicator)
sections = (, {
Name = ,
Position = .(, ),
Size = .(, -, , -),
BackgroundTransparency = ,
ClipsDescendants = ,
ZIndex = ,
}, win)
banner = (, {
Name = ,
Position = .(, ),
Size = .(, -, , ),
BackgroundColor3 = THEME.panel2,
BorderSizePixel = ,
ZIndex = ,
}, win)
(banner, )
bannerStroke = (banner, THEME.line, , )
(, {
Rotation = ,
Color = ColorSequence.(.(, , ), .(, , )),
}, banner)
bannerDot = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, ),
Size = .(, ),
BackgroundColor3 = THEME.off,
BorderSizePixel = ,
ZIndex = ,
}, banner)
(bannerDot, )
(, {
Color = THEME.on,
Thickness = ,
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, bannerDot)
bannerText = (, {
Name = ,
BackgroundTransparency = ,
Position = .(, ),
Size = .(, -, , ),
FontFace = FONT_BOLD,
Text = ,
TextSize = ,
TextColor3 = THEME.dim,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, banner)
bannerMap = (, {
Name = ,
BackgroundTransparency = ,
AnchorPoint = .(, ),
Position = .(, -, , ),
Size = .(, -, , ),
FontFace = FONT,
Text = ,
TextSize = ,
TextColor3 = THEME.dim,
TextXAlignment = .TextXAlignment.Right,
TextTruncate = .TextTruncate.AtEnd,
ZIndex = ,
}, banner)
NAV_LABEL = { Roles = , Visuals = , HUD = , Tasks = , Teleport = , Extras = , Info = }
nav = {}
currentTab =
ToastStroke = (, {
Color = .(, , ),
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
})
Toast = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, , , -),
Size = .(, ),
AutomaticSize = .AutomaticSize.X,
BackgroundColor3 = THEME.panel,
BackgroundTransparency = ,
BorderSizePixel = ,
FontFace = FONT,
TextSize = ,
TextColor3 = .(, , ),
TextTransparency = ,
Text = ,
ZIndex = ,
}, win)
(Toast, )
(, { PaddingLeft = .(, ), PaddingRight = .(, ) }, Toast)
ToastStroke.Parent = Toast
.(fadeExclude, Toast)
toastId =
(text, color)
toastId +=
id = toastId
Toast.TextColor3 = color .(, , )
Toast.Position = .(, , , -)
(Toast, , { BackgroundTransparency = , TextTransparency = , Position = .(, , , -) })
(ToastStroke, , { Transparency = })
(Toast, text, )
.(, ()
id ~= toastId
(Toast, , { BackgroundTransparency = , TextTransparency = })
(ToastStroke, , { Transparency = })
)
AsciiHolder = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, ),
BackgroundTransparency = ,
ClipsDescendants = ,
Visible = ,
ZIndex = ,
}, gui)
.(fadeExclude, AsciiHolder)
asciiCfg = {
textSize = ,
padding = ,
fps = ,
ramp = ,
color = .(, , ),
mouse = ,
}
ascii = {}
asciiAlpha = (, { Value = , Name = })
font = .Font.Code
huge = .(, )
size = asciiCfg.textSize
cw = TextService:(.(, ), size, font, huge).X /
h1 = TextService:(, size, font, huge).Y
h11 = TextService:(.(, ) .. , size, font, huge).Y
ch = (h11 - h1) /
cw <=
cw = size *
ch <=
ch = size
layers = {
{ min = , transparency = },
{ min = , transparency = },
{ min = , transparency = },
}
labels = {}
i (layers)
labels[i] = (, {
Name = .. i,
BackgroundTransparency = ,
Font = font,
TextSize = size,
TextColor3 = asciiCfg.color,
TextTransparency = ,
TextXAlignment = .TextXAlignment.Left,
TextYAlignment = .TextYAlignment.Top,
TextWrapped = ,
RichText = ,
Text = ,
ZIndex = ,
}, AsciiHolder)
ramp = {}
i = , #asciiCfg.ramp
ramp[i] = asciiCfg.ramp:(i, i)
rampLen = #ramp
cols, rows, offX, offY = , , ,
glow = {}
bufs = { {}, {}, {} }
lines = { {}, {}, {} }
ascii.(area, inner)
cols = .(, .(area.X / cw))
rows = .(, .(area.Y / ch))
offX = (area.X - cols * cw) /
offY = (area.Y - rows * ch) /
_, l (labels)
l.Position = .(offX, offY)
l.Size = .(cols * cw + , rows * ch + )
cx, cy = area.X / , area.Y /
hw, hh = inner.X / , inner.Y /
pad = asciiCfg.padding
.(glow)
y = , rows -
qy = .(.(offY + (y + ) * ch - cy) - hh, )
x = , cols -
qx = .(.(offX + (x + ) * cw - cx) - hw, )
i = y * cols + x +
qx == qy ==
glow[i] = -
g = .( - .(qx * qx + qy * qy) / pad, , )
glow[i] = g * g * ( - * g)
_, b (bufs)
.(b)
noise, sin, sqrt, exp, floor = .noise, .sin, .sqrt, .exp, .floor
min1, min2, min3 = layers[].min, layers[].min, layers[].min
aspect = ch / cw
ascii.(t, alpha, mx, my)
mcx, mcy
mx
mcx, mcy = (mx - offX) / cw, (my - offY) / ch
b1, b2, b3 = bufs[], bufs[], bufs[]
l1, l2, l3 = lines[], lines[], lines[]
y = , rows -
base = y * cols
ny = y * - t *
wy = .(y * - t * )
x = , cols -
g = glow[base + x + ]
c1, c2, c3 = , ,
g >
v = + (x * , ny, t * ) * + (x * + t * ) * wy *
mcx
dx, dy = x - mcx, (y - mcy) * aspect
d = (dx * dx + dy * dy)
v += (d * - t * ) * (-d * ) *
v *= g * alpha
v >= min1
idx = .((v * rampLen) + , , rampLen)
glyph = ramp[idx]
v >= min3
c3 = glyph
v >= min2
c2 = glyph
c1 = glyph
b1[x + ], b2[x + ], b3[x + ] = c1, c2, c3
l1[y + ] = .(b1, , , cols)
l2[y + ] = .(b2, , , cols)
l3[y + ] = .(b3, , , cols)
labels[].Text = .(l1, , , rows)
labels[].Text = .(l2, , , rows)
labels[].Text = .(l3, , , rows)
i, layer (layers)
labels[i].TextTransparency = - ( - layer.transparency) * alpha
acc, lastArea = ,
start = .()
(RunService.RenderStepped, (dt)
AsciiHolder.Position = Main.Position
alpha = asciiAlpha.Value
alpha <= Main.Visible
acc += dt
acc < / asciiCfg.fps
acc =
s = MainScale.Scale > MainScale.Scale
abs = Main.AbsoluteSize
inner = .(.(abs.X / s + ), .(abs.Y / s + ))
area = inner + .(asciiCfg.padding * , asciiCfg.padding * )
area ~= lastArea
lastArea = area
AsciiHolder.Size = .(area.X, area.Y)
ascii.(area, inner)
mx, my
asciiCfg.mouse
m = UserInputService:() - AsciiHolder.AbsolutePosition
m.X >= m.Y >= m.X <= area.X m.Y <= area.Y
mx, my = m.X, m.Y
ascii.(.() - start, alpha, mx, my)
)
moveIndicator
()
gui.Enabled
gui.Enabled =
Main.Visible =
AsciiHolder.Visible =
MainScale.Scale =
(Main, , )
(MainScale, , { Scale = }, .EasingStyle.Back)
(Main, , )
(asciiAlpha, , { Value = }, .EasingStyle.Sine)
(Title, TITLE, )
.(()
moveIndicator
()
)
(cb)
gui.Enabled
cb
()
closeNotice
(closeNotice)
(MainScale, , { Scale = }, .EasingStyle.Quad, .EasingDirection.In)
(Main, , )
(asciiAlpha, , { Value = }, .EasingStyle.Quad)
.(, ()
Main.Visible =
gui.Enabled =
AsciiHolder.Visible =
cb
()
)
(closeBtn, ()
()
)
(page, text, order)
holder = (, {
Size = .(, , , ),
BackgroundTransparency = ,
LayoutOrder = order,
ZIndex = ,
}, page)
bar = (, {
Name = ,
AnchorPoint = .(, ),
Position = .(, , , ),
Size = .(, ),
BackgroundColor3 = THEME.line,
BorderSizePixel = ,
ZIndex = ,
}, holder)
(bar, )
(, {
Color = THEME.line,
Thickness = ,
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, bar)
(, {
BackgroundTransparency = ,
Position = .(, ),
Size = .(, -, , ),
FontFace = FONT_BOLD,
Text = .(text),
TextSize = ,
TextColor3 = THEME.dim,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, holder)
holder
(page, order)
c = (, {
Size = .(, , , ),
AutomaticSize = .AutomaticSize.Y,
BackgroundColor3 = THEME.panel,
BorderSizePixel = ,
LayoutOrder = order,
ZIndex = ,
}, page)
(c, )
cStroke = (c, .(, , ), , )
(, {
Padding = .(, ),
SortOrder = .SortOrder.LayoutOrder,
}, c)
(, {
PaddingTop = .(, ),
PaddingBottom = .(, ),
PaddingLeft = .(, ),
PaddingRight = .(, ),
}, c)
(c.MouseEnter, ()
(c, , { BackgroundColor3 = THEME.cardHover })
(cStroke, , { Transparency = })
)
(c.MouseLeave, ()
(c, , { BackgroundColor3 = THEME.panel })
(cStroke, , { Transparency = })
)
c
(parent, order, text, get, set)
row = (, {
Size = .(, , , ),
BackgroundTransparency = ,
Text = ,
AutoButtonColor = ,
LayoutOrder = order,
ZIndex = ,
}, parent)
label = (, {
BackgroundTransparency = ,
Position = .(, ),
Size = .(, -, , ),
FontFace = FONT,
Text = text,
TextSize = ,
TextColor3 = THEME.text,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, row)
sw = (, {
Position = .(, -, , -),
Size = .(, ),
BackgroundColor3 = THEME.off,
BorderSizePixel = ,
ZIndex = ,
}, row)
(sw, )
(sw, .(, , ), , )
knob = (, {
Size = .(, ),
Position = .(, ),
BackgroundColor3 = .(, , ),
BorderSizePixel = ,
ZIndex = ,
}, sw)
(knob, )
knobGlow = (, {
Color = THEME.bg,
Thickness = ,
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, knob)
()
on = ()
(sw, , { BackgroundColor3 = on THEME.on THEME.off })
knob.BackgroundColor3 = on THEME.bg .(, , )
knob.Position = on .(, -, , ) .(, )
knobGlow.Transparency = on
()
(row, ()
on = ()
(on)
()
goal = on .(, -, , ) .(, )
knob.Position = on .(, ) .(, -, , )
(knob, , { Position = goal }, .EasingStyle.Back)
()
)
(row.MouseEnter, ()
(label, , { TextColor3 = .(, , ) })
)
(row.MouseLeave, ()
(label, , { TextColor3 = THEME.text })
)
refresh
(parent, order, text, callback)
b = (, {
Size = .(, , , ),
BackgroundColor3 = THEME.button,
BorderSizePixel = ,
FontFace = FONT_BOLD,
Text = text,
TextSize = ,
TextColor3 = THEME.text,
AutoButtonColor = ,
LayoutOrder = order,
ZIndex = ,
}, parent)
(b, )
(b, .(, , ), , )
(, {}, b)
(b,
{ BackgroundColor3 = THEME.button, TextColor3 = THEME.text },
{ BackgroundColor3 = THEME.buttonHi, TextColor3 = .(, , ) })
(b, callback)
b
(parent, order, text, min, max, get, set, fmt, snap)
row = (, {
Size = .(, , , ),
BackgroundTransparency = ,
LayoutOrder = order,
ZIndex = ,
}, parent)
(, {
BackgroundTransparency = ,
Position = .(, ),
Size = .(, -, , ),
FontFace = FONT,
Text = text,
TextSize = ,
TextColor3 = THEME.text,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, row)
valLbl = (, {
BackgroundTransparency = ,
Position = .(, ),
Size = .(, -, , ),
FontFace = FONT_BOLD,
Text = ,
TextSize = ,
TextColor3 = THEME.accent,
TextXAlignment = .TextXAlignment.Right,
ZIndex = ,
}, row)
track = (, {
Position = .(, ),
Size = .(, -, , ),
BackgroundColor3 = THEME.panel2,
BorderSizePixel = ,
ZIndex = ,
}, row)
(track, )
(track, .(, , ), , )
fill = (, {
Size = .(, ),
BackgroundColor3 = THEME.accent,
BorderSizePixel = ,
ZIndex = ,
}, track)
(fill, )
(, {
Color = THEME.accent,
Thickness = ,
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, fill)
knob = (, {
Size = .(, ),
AnchorPoint = .(, ),
Position = .(, , , ),
BackgroundColor3 = THEME.bg,
BorderSizePixel = ,
ZIndex = ,
}, track)
(knob, )
(, {
Color = .(, , ),
Thickness = ,
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, knob)
()
v = ()
a = .((v - min) / (max - min), , )
fill.Size = .(a, )
knob.Position = .(a, , , )
valLbl.Text = (fmt (v)) .(, v)
()
dragging =
(input)
rel = .((input.Position.X - track.AbsolutePosition.X) / .(, track.AbsoluteSize.X), , )
v = min + rel * (max - min)
snap snap >
v = .(v / snap + ) * snap
v = .(v * + ) /
(v)
()
hit = (, {
Position = .(, ),
Size = .(, , , ),
BackgroundTransparency = ,
Text = ,
AutoButtonColor = ,
ZIndex = ,
}, row)
(hit.InputBegan, (input)
input.UserInputType == .UserInputType.MouseButton1 input.UserInputType == .UserInputType.Touch
dragging =
(input)
)
(hit.InputEnded, (input)
input.UserInputType == .UserInputType.MouseButton1 input.UserInputType == .UserInputType.Touch
dragging =
()
)
(UserInputService.InputChanged, (input)
dragging (input.UserInputType == .UserInputType.MouseMovement input.UserInputType == .UserInputType.Touch)
(input)
)
render
PALETTE = {
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
{ , , },
}
colorSyncers = {}
(parent, order, text, get, set)
(c, i)
v = c[i]
(v) ~=
v = c[(i)]
(v) ~=
v =
.(v, , )
refresh
row = (, {
Size = .(, , , ),
AutomaticSize = .AutomaticSize.Y,
BackgroundTransparency = ,
LayoutOrder = order,
ZIndex = ,
}, parent)
(, {
Padding = .(, ),
SortOrder = .SortOrder.LayoutOrder,
}, row)
head = (, {
Size = .(, , , ),
BackgroundTransparency = ,
LayoutOrder = ,
ZIndex = ,
}, row)
(, {
BackgroundTransparency = ,
Position = .(, ),
Size = .(, , , ),
FontFace = FONT,
Text = text,
TextSize = ,
TextColor3 = THEME.text,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, head)
preview = (, {
Position = .(, ),
Size = .(, ),
BackgroundColor3 = (()),
BorderSizePixel = ,
Text = ,
AutoButtonColor = ,
ZIndex = ,
}, head)
(preview, )
(preview, .(, , ), , )
(, {
Color = THEME.accent,
Thickness = ,
Transparency = ,
ApplyStrokeMode = .ApplyStrokeMode.Border,
}, preview)
i, c (PALETTE)
s = (, {
Position = .( + (i - ) * , ),
Size = .(, ),
BackgroundColor3 = (c),
BorderSizePixel = ,
Text = ,
AutoButtonColor = ,
ZIndex = ,
}, head)
(s, )
s.MouseEnter:(()
(s, , { Size = .(, ) })
)
s.MouseLeave:(()
(s, , { Size = .(, ) })
)
(s, ()
({ c[], c[], c[] })
()
()
)
picker = (, {
Size = .(, , , ),
AutomaticSize = .AutomaticSize.Y,
BackgroundTransparency = ,
LayoutOrder = ,
Visible = ,
ZIndex = ,
}, row)
(, {
Padding = .(, ),
SortOrder = .SortOrder.LayoutOrder,
}, picker)
hexRow = (, {
Size = .(, , , ),
BackgroundTransparency = ,
LayoutOrder = ,
ZIndex = ,
}, picker)
(, {
BackgroundTransparency = ,
Position = .(, ),
Size = .(, , , ),
FontFace = FONT_BOLD,
Text = ,
TextSize = ,
TextColor3 = THEME.dim,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, hexRow)
hexBox = (, {
Position = .(, ),
Size = .(, , , ),
BackgroundColor3 = THEME.panel2,
BorderSizePixel = ,
FontFace = FONT_BOLD,
Text = ,
PlaceholderText = ,
TextSize = ,
TextColor3 = THEME.text,
TextXAlignment = .TextXAlignment.Center,
ClearTextOnFocus = ,
ZIndex = ,
}, hexRow)
(hexBox, )
(hexBox, .(, , ), , )
bigPrev = (, {
AnchorPoint = .(, ),
Position = .(, -, , ),
Size = .(, ),
BackgroundColor3 = (()),
BorderSizePixel = ,
ZIndex = ,
}, hexRow)
(bigPrev, )
(bigPrev, .(, , ), , )
(str)
(str) ~=
s = str:(, ):(, )
#s ~= s:()
{ (s:(, ), ), (s:(, ), ), (s:(, ), ) }
rR = (picker, , , , ,
() c = (); (c, ) ,
(v) c = (); ({ v, (c, ), (c, ) }) ,
(v) .(, v) , )
rG = (picker, , , , ,
() c = (); (c, ) ,
(v) c = (); ({ (c, ), v, (c, ) }) ,
(v) .(, v) , )
rB = (picker, , , , ,
() c = (); (c, ) ,
(v) c = (); ({ (c, ), (c, ), v }) ,
(v) .(, v) , )
()
c = ()
preview.BackgroundColor3 = (c)
bigPrev.BackgroundColor3 = (c)
hexBox.Text = .(,
.((c, ) + ), .((c, ) + ), .((c, ) + ))
(rR)
(rG)
(rB)
colorSyncers[#colorSyncers + ] = refresh
()
(hexBox.FocusLost, ()
c = (hexBox.Text)
c
(c)
()
()
)
(preview, ()
picker.Visible = picker.Visible
()
)
refresh
(parent, order, text)
row = (, {
Size = .(, , , ),
BackgroundTransparency = ,
LayoutOrder = order,
ZIndex = ,
}, parent)
lbl = (, {
BackgroundTransparency = ,
Position = .(, ),
Size = .(, -, , ),
FontFace = FONT,
Text = text,
TextSize = ,
TextColor3 = THEME.dim,
TextXAlignment = .TextXAlignment.Left,
ZIndex = ,
}, row)
lbl
moveIndicator = (instant)
currentTab
btn = nav[currentTab].btn
s = MainScale.Scale > MainScale.Scale
x = (btn.AbsolutePosition.X + btn.AbsoluteSize.X / - bottomBar.AbsolutePosition.X) / s
pos = .(, x, , -)
instant
indicator.Position = pos
(indicator, , { Position = pos })
(screen)
kids = {}
_, c (screen:())
c:()
kids[#kids + ] = c
.(kids, (a, b)
(a.LayoutOrder ) < (b.LayoutOrder )
)
i, c (kids)
(c, , )
.((i - ) * , ()
c.Parent
(c, , )
)
so = (screen)
prop, v (so)
(()
screen[prop] = v
)
(index, instant)
index == currentTab
prev = currentTab
dir = (prev index < prev) -
currentTab = index
i, tab (nav)
goal = (i == index) activeFx idleFx
instant
k, v ((tab.btn, goal))
tab.btn[k] = v
(tab.btn, , (tab.btn, goal))
(instant)
screen = nav[index].page
prev
old = nav[prev].page
instant
old.Visible =
(old, , )
(old, , { Position = .(- * dir, ) })
.(, ()
nav[currentTab] nav[currentTab].page ~= old
old.Visible =
)
screen.Visible =
instant
screen.Position = .()
(screen, , )
screen.Position = .( * dir, )
(screen, , { Position = .() })
(screen)
TP
TP.active = (nav[index].name == )
TP.active TP.refresh
.(TP.refresh)
selectTab = (idx)
(idx) ==
i, t (nav)
t.name == idx .(t.name) == .(idx)
idx = i
(idx) ~=
(idx, gui.Enabled)
(name, order)
btn = (, {
Name = name,
Size = .(, ),
BackgroundColor3 = THEME.button,
BorderSizePixel = ,
FontFace = FONT_BOLD,
Text = NAV_LABEL[name] name,
TextSize = ,
TextColor3 = THEME.icon,
AutoButtonColor = ,
LayoutOrder = order,
ZIndex = ,
}, buttonContainer)
(btn, )
(btn, .(, , ), , )
(, {}, btn)
page = (, {
Name = name .. ,
Size = .(, ),
BackgroundTransparency = ,
BorderSizePixel = ,
ScrollBarThickness = ,
ScrollBarImageColor3 = THEME.button,
CanvasSize = .(),
AutomaticCanvasSize = .AutomaticSize.Y,
ScrollingDirection = .ScrollingDirection.Y,
Visible = ,
ZIndex = ,
}, sections)
(, {
Padding = .(, ),
SortOrder = .SortOrder.LayoutOrder,
}, page)
(, {
PaddingTop = .(, ),
PaddingBottom = .(, ),
PaddingLeft = .(, ),
PaddingRight = .(, ),
}, page)
nav[#nav + ] = { btn = btn, page = page, name = name }
idx = #nav
(btn, idleFx, hoverFx, ()
currentTab == idx
)
(btn, ()
(idx)
)
page
(bottomBar:(), ()
()
)
(bottomBar:(), ()
()
)
espPage = (, )
stylePage = (, )
hudPage = (, )
tasksPage = (, )
tpPage = (, )
extrasPage = (, )
infoPage = (, )
(espPage, , )
c1 = (espPage, )
(c1, , , ()
STATE.enabled
, (v)
STATE.enabled = v
)
(espPage, , )
c2 = (espPage, )
i, role ({ , , , })
(c2, i, role, ()
STATE.roles[role]
, (v)
STATE.roles[role] = v
)
(c2, , , ()
STATE.self
, (v)
STATE.self = v
)
(espPage, , )
c3 = (espPage, )
(c3, , , , , ()
STATE.maxDist
, (v)
STATE.maxDist = v
, (v)
.(, v)
, )
(espPage, , )
c4 = (espPage, )
(c4, , , ()
STATE.labels
, (v)
STATE.labels = v
)
(c4, , , ()
STATE.showDistance
, (v)
STATE.showDistance = v
)
(stylePage, , )
s1 = (stylePage, )
(s1, , , ()
STATE.throughWalls
, (v)
STATE.throughWalls = v
)
(s1, , , , , ()
STATE.fillTransparency
, (v)
STATE.fillTransparency = v
, (v)
.(, .(( - v) * + ))
)
(stylePage, , )
s2 = (stylePage, )
i, role ({ , , , , })
(s2, i, role, ()
STATE.colors[role]
, (c)
STATE.colors[role] = c
)
(stylePage, , )
s3 = (stylePage, )
(s3, , , ()
STATE.freeMouse
, (v)
STATE.freeMouse = v
)
s3note = (s3, , )
s3note2 = (s3, , )
s3note3 = (s3, , )
(stylePage, , )
s4 = (stylePage, )
(s4, , , ()
STATE.fullbright
, (v)
STATE.fullbright = v
)
(s4, , , ()
STATE.noFog
, (v)
STATE.noFog = v
)
(s4, , , , , ()
STATE.cameraFov
, (v)
STATE.cameraFov = v
, (v)
.(, v)
, )
(s4, , )
(s4, , )
(hudPage, , )
h1 = (hudPage, )
(h1, , , ()
STATE.hud
, (v)
STATE.hud = v
)
(h1, , , ()
STATE.offscreen
, (v)
STATE.offscreen = v
)
(h1, , , ()
STATE.tracers
, (v)
STATE.tracers = v
)
(h1, , , ()
STATE.fpsCounter
, (v)
STATE.fpsCounter = v
)
(h1, , , ()
STATE.pingCounter
, (v)
STATE.pingCounter = v
)
(hudPage, , )
hpos = (hudPage, )
(hpos, , )
(hpos, , )
(hpos, , , ()
STATE.hudPos =
hud
hud.AnchorPoint = .(, )
hud.Position = .(, -, , )
()
)
(hudPage, , )
h2 = (hudPage, )
(h2, , , ()
STATE.monsterAlert
, (v)
STATE.monsterAlert = v
)
(h2, , , , , ()
STATE.alertDist
, (v)
STATE.alertDist = v
, (v)
.(, v)
, )
(tasksPage, , )
tk1 = (tasksPage, )
(tk1, , , ()
STATE.shardEsp
, (v)
STATE.shardEsp = v
)
(tk1, , , ()
STATE.autoCollect
, (v)
STATE.autoCollect = v
)
(tk1, , , , , ()
STATE.collectDelay
, (v)
STATE.collectDelay = v
, (v)
.(, v)
, )
(tk1, , , , , ()
STATE.collectHeight
, (v)
STATE.collectHeight = v
, (v)
.(, v)
, )
(tasksPage, , )
tk2 = (tasksPage, )
(tk2, , , ()
STATE.shardColor
, (c)
STATE.shardColor = c
)
(tasksPage, , )
tk3 = (tasksPage, )
(tk3, , )
(tk3, , )
(tk3, , )
(tk3, , )
(tk3, , )
(tk3, , )
(tk3, , )
(tk3, , )
(tasksPage, , )
tk4 = (tasksPage, )
infoStatus = (tk4, , )
infoMap = (tk4, , )
infoObj1 = (tk4, , )
infoObj2 = (tk4, , )
infoExit = (tk4, , )
infoShards = (tk4, , )
(tasksPage, , )
tk5 = (tasksPage, )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , )
(tk5, , , ()
()
)
TP = {}
(tpPage, , )
tp1 = (tpPage, )
TP.status = (tp1, , )
TP.map = (tp1, , )
TP.obj1 = (tp1, , )
TP.obj2 = (tp1, , )
TP.exit = (tp1, , )
(tp1, , , ()
TP.refresh
TP.()
)
(tpPage, , )
TP.list = (tpPage, )
(tpPage, , )
tp3 = (tpPage, )
(tp3, , , ()
TP.goNearest
TP.()
)
(tp3, , , ()
TP.goExit
TP.()
)
(tp3, , )
(tp3, , )
(tp3, , )
(tp3, , )
(tpPage, , )
TP.players = (tpPage, )
(tpPage, , )
tp4 = (tpPage, )
(tp4, , )
(tp4, , )
(tp4, , )
(tp4, , )
(tp4, , )
(tp4, , )
(extrasPage, , )
x1 = (extrasPage, )
(x1, , , ()
STATE.antiAfk
, (v)
STATE.antiAfk = v
)
(x1, , )
(x1, , )
(extrasPage, , )
x2 = (extrasPage, )
(x2, , , ()
STATE.exitEsp
, (v)
STATE.exitEsp = v
)
(x2, , , ()
STATE.exitColor
, (c)
STATE.exitColor = c
)
(x2, , )
(x2, , )
(extrasPage, , )
x3 = (extrasPage, )
(x3, , , ()
STATE.crosshair
, (v)
STATE.crosshair = v
)
(x3, , , ()
STATE.crosshairColor
, (c)
STATE.crosshairColor = c
)
(extrasPage, , )
x5 = (extrasPage, )
(x5, , , ()
STATE.infiniteStamina
, (v)
STATE.infiniteStamina = v
)
(x5, , )
(x5, , )
(x5, , )
(x5, , , ()
STATE.noclip
, (v)
STATE.noclip = v
)
(x5, , )
(x5, , )
(x5, , )
(x5, , , , , ()
STATE.speedMult
, (v)
STATE.speedMult = v
, (v)
.(, v)
, )
(x5, , )
(x5, , )
(extrasPage, , )
x4 = (extrasPage, )
(x4, , )
(x4, , )
(x4, , )
(x4, , )
keybindBtn = (x4, , .. (STATE.keybind) .. , ()
keybindCapturing =
keybindBtn.Text =
)
ture: 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.")Find out after you execute (no its not a virus lol)

Flee the Facility