Run it
Copy the script and paste it into your executor.
ALLOWED_PLACES = {
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
}
allowed = ALLOWED_PLACES[.PlaceId] ==
allowed
(()
:() .Loaded:()
assets = :():(, )
remotes = assets assets:(, )
allowed = remotes ~= remotes:() ~= remotes:() ~=
)
allowed
(()
:().LocalPlayer:()
)
MENU_PLACE_ID =
MIN_DELAY, MAX_DELAY = ,
Players = :()
HttpService = :()
GuiService = :()
ReplicatedStorage = :()
VirtualInputManager = :()
LocalPlayer = Players.LocalPlayer
PlayerGui = LocalPlayer:()
IN_MENU = (.PlaceId == MENU_PLACE_ID)
SH = { DEV = }
SH.(...) (, ...)
SH.(...) SH.DEV (...)
SH.(name, fn)
ok, err = (fn, (e) (e) .. .. debug.() )
ok ( .. (name) .. .. (err))
SH.( .. (IN_MENU ) .. )
(.SmoothHubCleanup) == (.SmoothHubCleanup)
token = {}
.SmoothHubReplayToken = token
() .SmoothHubReplayToken == token
AppRef =
.SmoothHubCleanup = ()
(() AppRef AppRef.Destroy AppRef:() )
Cfg = {
Slot = ,
AutoSelect = ,
AutoJoin = ,
JoinDelay = ,
AutoClaimMain = ,
AutoClaimSide = ,
AutoClaimAchievements = ,
AutoClaimWeekly = ,
AutoClaimDaily = ,
AutoOpenPages = ,
AutoQuestThunder = ,
AutoQuestThunderR1 = ,
AutoQuestThunderR2 = ,
AutoQuestThunderR3 = ,
QuestR3Played = ,
AutoUpgradeBlade = ,
AutoUpgradeThunder = ,
AutoEquipThunder = ,
AutoEquipBlade = ,
UseThunderSpears = ,
MissionSelect = ,
MissionObjective = ,
MissionDifficulty = {},
MissionModifiers = {},
AutoBestGrade = ,
AutoBoostMission = ,
AutoCreateMission = ,
AutoStartMission = ,
MissionLevel = ,
MissionJoinDelay = ,
RaidSelect = ,
RaidModifiers = {},
RaidLevel = ,
AutoCreateRaid = ,
AutoStartRaid = ,
RaidJoinDelay = ,
WaveSelect = ,
WaveLevel = ,
AutoCreateWave = ,
AutoStartWave = ,
WaveJoinDelay = ,
AutoRefillWave = ,
AutoRollBlessing = ,
RollStopOnMatch = ,
AutoHealInjuries = ,
AutoUpgradeChar = ,
AutoUpgradeBlades = ,
UpgMaxHP = ,
UpgRegen = ,
UpgReplenish = ,
UpgRefills = ,
UpgRevive = ,
AutoLootCrate = ,
AutoShop = ,
ShopPerk = ,
ShopArtifact = ,
ShopMemory = ,
ShopCrates = ,
ShopStar1 = , ShopStar2 = , ShopStar3 = , ShopStar4 = , ShopStar5 = ,
ShopPerkStar1 = , ShopPerkStar2 = , ShopPerkStar3 = , ShopPerkStar4 = , ShopPerkStar5 = ,
ShopArtifactStar1 = , ShopArtifactStar2 = , ShopArtifactStar3 = , ShopArtifactStar4 = , ShopArtifactStar5 = ,
ShopMemoryStar1 = , ShopMemoryStar2 = , ShopMemoryStar3 = , ShopMemoryStar4 = , ShopMemoryStar5 = ,
ShopPriority = ,
ShopMaxPrice = ,
ShopReserve = ,
WaveMinGold = ,
AutoSpin = ,
SpinStopRarity = ,
SpinShowList = ,
SpinListSize = ,
SpinUseDelay = ,
SpinDelay = ,
SpinOnKeep = ,
SpinFamilyRules = ,
AutoFarm = ,
AutoPlay = ,
AutoClearWave = ,
ClearWaveRange = ,
AutoFarmThunder = ,
ThunderTargets = ,
ThunderRange = ,
AutoFarmSmart = ,
FarmMode = ,
RefillEnabled = ,
RefillPickBlade = ,
RefillPickThunder = ,
SafeFarm = ,
AutoReload = ,
AutoRefillsBlade = ,
AutoRefillsThunder = ,
DamageMultiplier = ,
SafeTime = ,
FailSafeAt = ,
AutoFailSafe = ,
Streak = ,
NoClip = ,
HitAllParts = ,
FlySpeed = ,
StartDelay = ,
PartCooldown = ,
FlyHeight = ,
WaitForBoss = ,
BossSettle = ,
AttackSpeed = ,
AutoOpenChestFree = ,
AutoCannon = ,
CannonFarmSmall = ,
AutoDefendEren = ,
DefendErenRadius = ,
AutoFinishChest = ,
AutoOpenChestPremium = ,
AutoSkipCutscene = ,
AutoRetry = ,
AutoLeave = ,
AutoReady = ,
AutoVoteSkip = ,
AutoLeaveAtWave = ,
LeaveAtWave = ,
AutoSkill1 = , AutoSkill2 = , AutoSkill3 = , AutoSkill4 = , AutoSkill5 = ,
AutoSkillMode1 = , AutoSkillMode2 = , AutoSkillMode3 = , AutoSkillMode4 = , AutoSkillMode5 = ,
AutoSkillHoldTime = ,
AutoSkillDelay = ,
RetryAmount = ,
RetryDone = ,
AutoLeaveWhenRetryComplete = ,
AutoUpgradeSkills = ,
SkillLeft = ,
SkillMiddle = ,
SkillRight = ,
SkillLeftPick = ,
SkillMiddlePick = ,
SkillRightPick = ,
SkillLeftPriority = ,
SkillMiddlePriority = ,
SkillRightPriority = ,
AutoCraftPerks = ,
CraftCommonRare = ,
CraftRareEpic = ,
CraftEpicLegendary = ,
CraftLegendaryMythic = ,
CraftChanceCommonRare = ,
CraftChanceRareEpic = ,
CraftChanceEpicLegendary = ,
CraftChanceLegendaryMythic = ,
AutoPrestige = ,
PrestigeBoost1 = ,
PrestigeBoost2 = ,
PrestigeBoost3 = ,
PrestigeBoost4 = ,
PrestigeBoost5 = ,
PrestigeStopAt = ,
}
SH.() Cfg.AutoPlay ==
SH.Coord = { jobs = {}, last = }
SH.Coord.(name, prio, pending, busy, giveUp)
SH.Coord.jobs[name] = { prio = prio, pending = pending, busy = busy, giveUp = giveUp , since = , seen = , warned = }
SH.Coord.(askerPrio)
now = .()
found =
name, j (SH.Coord.jobs)
okB, isBusy = (j.busy)
okP, isPending = (j.pending)
isBusy = okB isBusy ==
isPending = okP isPending ==
isBusy isPending
j.since = j.since now
j.seen now - j.seen > j.since = now
j.seen = now
( isBusy) now - j.since > j.giveUp
j.warned
j.warned =
SH.(name .. )
j.prio < askerPrio found
found = name
j.since, j.seen, j.warned = , ,
SH.Coord.last = found
found
FARM_RANGES = {
SafeTime = { , },
FailSafeAt = { , },
FlySpeed = { , },
StartDelay = { , },
PartCooldown = { , },
FlyHeight = { , },
BossSettle = { , },
LeaveAtWave = { , },
ShopMaxPrice = { , },
ShopReserve = { , },
ClearWaveRange = { , },
WaveMinGold = { , },
AutoSkillHoldTime = { , },
AutoSkillDelay = { , },
AttackSpeed = { , },
RetryAmount = { , },
SpinListSize = { , },
DefendErenRadius = { , },
ThunderTargets = { , },
ThunderRange = { , },
SkillLeftPriority = { , },
SkillMiddlePriority = { , },
SkillRightPriority = { , },
CraftChanceCommonRare = { , },
CraftChanceRareEpic = { , },
CraftChanceEpicLegendary = { , },
CraftChanceLegendaryMythic = { , },
PrestigeStopAt = { , },
}
SH.Memories = {
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
{ Id = , Name = , Stars = , Desc = },
}
_, m (SH.Memories)
m.Key = .. m.Id
Cfg[m.Key] =
FARM_RANGES[m.Key] = { , }
UiSync = { Defaults = { Cfg = {}, Hub = {} }, Refreshers = {} }
key, value (Cfg)
(value) ==
copy = {}
i, item (value) copy[i] = item
UiSync.Defaults.Cfg[key] = copy
UiSync.Defaults.Cfg[key] = value
UiSync.(fn)
UiSync.Refreshers[#UiSync.Refreshers + ] = fn
UiSync.(control, tbl, key, extra)
UiSync.(()
want = tbl[key]
control.Value ~= want control.Value = want
extra (control, want)
)
control
UiSync.()
_, fn (UiSync.Refreshers) (fn)
SAVE_DIR =
LEGACY_PATH = SAVE_DIR .. .. (LocalPlayer.UserId) ..
canSave = (writefile) == (readfile) == (isfile) ==
(v)
v = (v)
.(.(v + ), MIN_DELAY, MAX_DELAY)
(data)
(data) ~=
(data.Slot) == data.Slot >= data.Slot <= Cfg.Slot = .(data.Slot)
data.JoinDelay ~= Cfg.JoinDelay = (data.JoinDelay)
data.MissionJoinDelay ~= Cfg.MissionJoinDelay = (data.MissionJoinDelay)
data.RaidJoinDelay ~= Cfg.RaidJoinDelay = (data.RaidJoinDelay)
data.WaveJoinDelay ~= Cfg.WaveJoinDelay = (data.WaveJoinDelay)
_, key ({
, , , , ,
, , , , , , , , , ,
, , , ,
, , , , , , , , , , , , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , ,
, , , , , , , , ,
, , , , , ,
})
(data[key]) == Cfg[key] = data[key]
i = ,
old = data[ .. i]
(old) ==
_, p ({ , , })
data[p .. i] == Cfg[p .. i] = old
(data.RetryDone) == data.RetryDone == data.RetryDone data.RetryDone >=
Cfg.RetryDone = .(data.RetryDone)
(data.QuestR3Played) == data.QuestR3Played == data.QuestR3Played data.QuestR3Played >=
Cfg.QuestR3Played = .(data.QuestR3Played)
(data.Streak) == data.Streak == data.Streak data.Streak >=
Cfg.Streak = .(data.Streak)
key, range (FARM_RANGES)
(data[key]) == data[key] == data[key]
Cfg[key] = .(.(data[key] + ), range[], range[])
(data.SpinDelay) == data.SpinDelay == data.SpinDelay
Cfg.SpinDelay = .(.(data.SpinDelay * + ) / , , )
data.AutoCreateMission == (data.RememberAutoCreate) == Cfg.AutoCreateMission = data.RememberAutoCreate
data.AutoStartMission == (data.RememberAutoStart) == Cfg.AutoStartMission = data.RememberAutoStart
_, key ({ , , , , , , , , , , , , , , , , , , , , , , , , })
(data[key]) == Cfg[key] = data[key]
_, key ({ , , })
(data[key]) ==
names = {}
_, v (data[key])
(v) == names[#names + ] = v
Cfg[key] = names
()
canSave
ok, data = (()
(LEGACY_PATH)
HttpService:((LEGACY_PATH))
)
ok (data) == data
saveHook =
saveQueued =
()
saveQueued
saveQueued =
.(, ()
saveQueued =
saveHook (saveHook)
)
ConfigStore = {
DEFAULT_NAME = ,
DEFAULT_LOOK = { Theme = , Accent = , UIBlur = , Dropshadow = , Keybind = },
}
DEFAULT_CONFIG_NAME = ConfigStore.DEFAULT_NAME
DEFAULT_LOOK = ConfigStore.DEFAULT_LOOK
PATH = SAVE_DIR .. .. (LocalPlayer.UserId) ..
data = { Autoload = , Active = , AutoSync = , List = {} }
sigs = {}
defaultSnap =
()
canSave
((()
(makefolder) == (isfolder) == (SAVE_DIR)
(SAVE_DIR)
(PATH, HttpService:(data))
))
(v)
(v) ==
out = {}
_, item (v)
(item) == out[#out + ] = item
out
v
ConfigStore.()
data = { Autoload = , Active = , AutoSync = , List = {} }
sigs = {}
defaultSnap data.List[DEFAULT_CONFIG_NAME] = defaultSnap
canSave
ok, decoded = (()
(PATH)
HttpService:((PATH))
)
ok (decoded) ==
(decoded.List) ==
name, snap (decoded.List)
(name) == (snap) == name ~= DEFAULT_CONFIG_NAME data.List[name] = snap
(decoded.Autoload) == data.List[decoded.Autoload] data.Autoload = decoded.Autoload
(decoded.Active) == data.List[decoded.Active] data.Active = decoded.Active
decoded.AutoSync == data.AutoSync =
ConfigStore.()
names = {}
name (data.List) names[#names + ] = name
.(names, (a, b)
a == DEFAULT_CONFIG_NAME b ~= DEFAULT_CONFIG_NAME
b == DEFAULT_CONFIG_NAME
a:() < b:()
)
names
ConfigStore.(name) name == DEFAULT_CONFIG_NAME
ConfigStore.(name) data.List[name] ~=
ConfigStore.(name) data.List[name]
ConfigStore.() data.Autoload
ConfigStore.() data.Active
ConfigStore.() data.Autoload data.List[data.Autoload]
ConfigStore.(hub)
snap = { Cfg = {}, Hub = {} }
k, v (Cfg) snap.Cfg[k] = (v)
hub
k, v (hub)
t = (v)
t == t == t == snap.Hub[k] = v
ConfigStore.GetUI
okUi, ui = (ConfigStore.GetUI)
okUi (ui) == snap.UI = ui
snap
ConfigStore.(name, snap)
name == DEFAULT_CONFIG_NAME
data.List[name] = snap
sigs[name] =
()
ConfigStore.(name)
name == DEFAULT_CONFIG_NAME
data.List[name] =
sigs[name] =
data.Autoload == name data.Autoload =
data.Active == name data.Active =
()
ConfigStore.(name)
name ~= data.List[name]
data.Autoload = name
()
ConfigStore.(name)
name ~= data.List[name]
data.Active == name
data.Active = name
()
ConfigStore.(hub)
data.AutoSync ==
name = data.Active
name data.List[name]
name == DEFAULT_CONFIG_NAME
snap = ConfigStore.(hub)
okSig, sig = (() HttpService:(snap) )
okSig sigs[name] == sig
data.List[name] = snap
() sigs[name] = sig
ConfigStore.() data.AutoSync ~=
ConfigStore.(value)
data.AutoSync = value
()
ConfigStore.(hub)
name = data.Active
name data.List[name] ,
name == DEFAULT_CONFIG_NAME , name
snap = ConfigStore.(hub)
data.List[name] = snap
sigs[name] =
ok = ()
ok
okSig, sig = (() HttpService:(snap) )
okSig sigs[name] = sig
ok, name
(x, y)
(x) ~= (y)
(x) ==
#x ~= #y
i = , #x
x[i] ~= y[i]
x == y
(current, saved)
(saved) ~=
k, v (current)
saved[k] ~= (v, saved[k])
ConfigStore.(hub)
name = data.Active
saved = name data.List[name]
saved
snap = ConfigStore.(hub)
((snap.Cfg, saved.Cfg) (snap.Hub, saved.Hub) (snap.UI {}, saved.UI))
ConfigStore.(snap)
(snap) ~= (snap.Cfg) ~=
saved = snap.Cfg
k, v (saved)
cur = Cfg[k]
cur ~= (cur) == (v)
(v) ==
Cfg[k] = (v)
k == k == k == k ==
Cfg[k] = (v)
k ==
Cfg[k] = .(.(v), , )
k == v == v
Cfg[k] = .(.(v * + ) / , , )
FARM_RANGES[k] v == v
Cfg[k] = .(.(v + ), FARM_RANGES[k][], FARM_RANGES[k][])
Cfg[k] = v
saved.AutoCreateMission == (saved.RememberAutoCreate) == Cfg.AutoCreateMission = saved.RememberAutoCreate
saved.AutoStartMission == (saved.RememberAutoStart) == Cfg.AutoStartMission = saved.RememberAutoStart
ConfigStore.(hub, snap)
(snap) ~= (snap.Hub) ~=
k, v (snap.Hub)
hub[k] ~= (hub[k]) == (v) hub[k] = v
ConfigStore.(snap)
(snap) ~= (snap.UI) ~=
ConfigStore.UIApplier (ConfigStore.UIApplier, snap.UI)
ConfigStore.(hubDefaults)
snap = { Cfg = {}, Hub = {}, UI = {} }
k, v (UiSync.Defaults.Cfg) snap.Cfg[k] = (v)
hubDefaults
k, v (hubDefaults)
t = (v)
t == t == t == snap.Hub[k] = v
k, v (DEFAULT_LOOK) snap.UI[k] = v
defaultSnap = snap
data.List[DEFAULT_CONFIG_NAME] = snap
sigs[DEFAULT_CONFIG_NAME] =
ConfigStore.()
ConfigStore.()
SH.()
okLib, Cascade = (()
(:())()
)
okLib (Cascade) ~=
SH.()
SH.(, (Cascade))
(()
:():(, {
Title = , Text = , Duration = ,
})
)
SH.()
mainRed = .()
glowRed = .()
darkRed = .()
bgDeep = .()
bgView = .()
bgSide = .()
bgTitle = .()
bgMenu = .()
()
{
SwitchAccent = mainRed,
Selection = darkRed,
SelectionFocused = glowRed,
Toggle = { SwitchOn = mainRed },
Button = { FillPrimary = ColorSequence.(glowRed, darkRed) },
}
dark = ()
dark.Background = bgDeep
dark.View = bgView
dark.Sidebar = bgSide
dark.Titlebar = bgTitle
dark.MenuButton = { MenuBackground = bgMenu }
redTheme = {
_id = ,
Dark = dark,
Light = (),
}
Cascade Cascade.Accents
_, name ({, , , , , , , , })
Cascade.Accents[name] = redTheme
(...)
_, name ({ ... })
Cascade.Symbols[name] Cascade.Symbols[name]
Cascade.Symbols[]
(btn, fn)
last =
()
.() - last <
last = .()
.(fn)
(()
btn.__instance
obj = btn.__instance:(, ) btn.__instance
obj.MouseButton1Click:(run)
)
run
SettingsStore = {}
HttpService = :()
owner = :().LocalPlayer
ROOT =
DIR = ROOT .. .. (owner.UserId)
canSave = (writefile) == (readfile) == (isfile) ==
canFolder = (makefolder) == (isfolder) ==
flushers = {}
SettingsStore.(fileName)
canFolder
DIR .. .. fileName ..
ROOT .. .. (owner.UserId) .. .. fileName ..
()
canFolder
(()
(ROOT) (ROOT)
(DIR) (DIR)
)
(value)
(value) ~= value
out = {}
k, v (value)
out[k] = (v)
out
(value, depth)
depth = depth
kind = (value)
kind == kind == value
kind ==
value ~= value value == .huge value == -.huge
value
kind ~= depth >
count, isArray = ,
k (value)
count = count +
(k) ~= isArray =
isArray
i = , count
value[i] ==
isArray =
out = {}
isArray
i = , count
c = (value[i], depth + )
c ~= .(out, c)
k, v (value)
c = (v, depth + )
c ~= out[(k)] = c
out
SettingsStore.(fileName)
canSave {}
ok, data = (()
path = SettingsStore.(fileName)
(path)
HttpService:((path))
)
ok (data) == data
{}
SettingsStore.(fileName, data)
canSave
()
ok = (()
(SettingsStore.(fileName), HttpService:(data))
)
ok
SettingsStore.(target, saved)
(target) ~= (saved) ~=
key, default (target)
value = saved[key]
value ~=
(default) ==
(value) ==
(default) ==
target[key] = (value)
SettingsStore.(default, value)
(default) == (value)
target[key] = value
SettingsStore.(options, name, fallback)
.(options, name) fallback
SettingsStore.(options, map)
out = {}
(map) ==
i, name (options)
map[name] .(out, i)
out
SettingsStore.(list, max)
out, seen = {}, {}
(list) ==
_, index (list)
(index) == index >= index <= max index % == seen[index]
seen[index] =
.(out, index)
out
SettingsStore.(fileName, getData, interval)
flushers[fileName]
last =
()
ok, snapshot = (()
(())
)
ok (snapshot) ~=
okSig, signature = (()
HttpService:(snapshot)
)
okSig signature == last
snapshot.SavedAt = .()
snapshot.Account = owner.Name
SettingsStore.(fileName, snapshot)
last = signature
SettingsStore.OnSaved (SettingsStore.OnSaved, snapshot.SavedAt)
flushers[fileName] = flush
.(()
.(interval )
()
)
SettingsStore.(fileName)
flush = flushers[fileName]
flush (flush)
SavedData = SettingsStore.()
SavedUi = (SavedData.UI) == SavedData.UI {}
autoSnap = ConfigStore.()
autoSnap (autoSnap.UI) ==
k, v (autoSnap.UI) SavedUi[k] = v
(SavedData.Cfg) == (SavedData.Cfg) (())
ConfigStore.()
ConfigStore.(ConfigStore.())
ConfigStore.(ConfigStore.())
SH.Legacy = {}
(n, lo, hi) .(lo, .(hi, n))
(src)
src = (src )
#src > ,
pos, len = , #src
ParseValue
()
pos <= len
c = src:(pos, pos)
c:()
pos = pos +
src:(pos, pos + ) ==
lb = src:(, pos)
lb
close = .. lb ..
e = src:(close, pos, )
pos = e (e + #close) (len + )
e = src:(, pos, )
pos = e (e + ) (len + )
ESC = { n = , t = , r = , [] = , [] = , [] = }
()
q = src:(pos, pos)
pos = pos +
buf = {}
pos <= len
c = src:(pos, pos)
c == q
pos = pos +
.(buf)
c ==
n = src:(pos + , pos + )
buf[#buf + ] = ESC[n] n
pos = pos +
buf[#buf + ] = c
pos = pos +
()
(depth)
depth > ()
pos = pos +
t, arr = {},
()
c = src:(pos, pos)
c == ()
c ==
pos = pos +
t
c == c ==
pos = pos +
c == src:(, pos)
pos = pos +
()
key = (depth + )
()
src:(pos, pos) ~= ()
pos = pos +
()
src:(pos, pos) ~= ()
pos = pos +
value = (depth + )
key ~= t[key] = value
name = src:(, pos)
name
pos = pos + #name
()
pos = pos +
t[name] = (depth + )
arr = arr +
t[arr] = (depth + )
ParseValue = (depth)
()
c = src:(pos, pos)
c ==
(depth)
c == c ==
()
c ==
lb = src:(, pos)
lb ()
close = .. lb ..
s = pos + #lb +
e = src:(close, s, )
e ()
pos = e + #close
src:(s, e - )
numStr = src:(, pos)
numStr
n = ((numStr:(, )))
n ()
pos = pos + #numStr
n
word = src:(, pos)
word == pos = pos +
word == pos = pos +
word == pos = pos +
( .. pos)
s = src:(, , )
s ,
pos = s
ok, res = (ParseTable, )
ok , .. (res):(, )
res
(s) ((s):():(, ))
(t, key)
(t) ~=
want = (key)
k, v (t)
(k) == (k) == want v
SLOT_NUM = { A = , B = , C = }
SKILL_PICKS = {
Left = { , },
Middle = { , },
Right = { , },
}
(list, v)
_, x (list) x == v
UNSUPPORTED = {
, , , , , ,
}
(tbl)
cfg, hub, applied, skipped = {}, {}, {}, {}
(key, value, label)
Cfg[key] ~= (Cfg[key]) == (value)
cfg[key] = value
applied[#applied + ] = label key
skipped[#skipped + ] = label key
(label) skipped[#skipped + ] = label
(v) (v) == v
(v) (v) == v == v v
v = ((tbl, ))
v ~= (, v, )
p = (tbl, )
(p) ==
e = ((p, ))
e ~= (, e, )
s = ((p, ))
s (, (.(s + ), , ), )
(p, ) ~= ()
slot = (tbl, )
slotN = (slot) == SLOT_NUM[slot:()] ((slot) (.(slot), , ))
slotN
(, slotN, )
(, , )
(tbl, ) ==
(, , )
(, , )
(, , )
(, , )
fam = (tbl, )
(fam) ==
rules, any = {},
(list)
(list) ~=
_, name (list)
(name) ==
name = name:(, ):()
name ~= rules[name:()] = any =
roll = (fam, )
(roll) ==
e = ((roll, ))
e ~= (, e, )
((roll, ))
(roll, ) ~= ()
st = (fam, )
(st) == (st, ) ==
(, , )
((st, ))
any
parts = {}
k, r (rules) parts[#parts + ] = k .. .. r
.(parts)
(, .(parts, ), )
cp = (tbl, )
(cp) ==
e = ((cp, ))
e ~= (, e, )
ch = (cp, )
(ch) ==
src, key ({ Rare = , Epic = ,
Legendary = , Mythic = })
n = ((ch, src))
n (key, (.(n + ), , ), .. src)
src, key ({ Epic = , Legendary = , Mythic = })
b = ((cp, .. src))
b ~= (key, b, .. src)
sk = (tbl, )
(sk) ==
e = ((sk, ))
e ~= (, e, )
_, lane ({ , , })
node = (sk, lane)
(node) ==
on = ((node, ))
on ~= ( .. lane, on, .. lane)
pick = (node, )
(pick) ==
(SKILL_PICKS[lane], pick) ( .. lane .. , pick, .. lane .. )
( .. lane .. .. pick .. )
(sk, ) ~= ()
perf = (tbl, )
(perf) ==
lock = (perf, )
(lock) ==
on = ((lock, ))
on ~= hub.EnableFPSLock = on applied[#applied + ] =
f = ((lock, ))
f hub.FPSLimit = (.(f + ), , ) applied[#applied + ] =
(perf, ) ~= ()
_, name (UNSUPPORTED)
(tbl, name) ~= (name)
{ cfg = cfg, hub = hub, applied = applied, skipped = skipped }
SH.Legacy.Parse = Parse
SH.Legacy.Translate = Translate
SH.Legacy.(res)
s = #res.applied ..
#res.skipped > s = s .. .. #res.skipped ..
s
okEnv, genv = (() () == () )
legacy = okEnv genv genv.Configs
(legacy) ==
ok, res = (Translate, legacy)
ok res
ConfigStore.({ Cfg = res.cfg })
SH.LegacyHub = res.hub
SH.( .. SH.Legacy.(res))
#res.skipped > SH.( .. .(res.skipped, ))
Cfg.JoinDelay = (Cfg.JoinDelay)
Cfg.MissionJoinDelay = (Cfg.MissionJoinDelay)
Cfg.RaidJoinDelay = (Cfg.RaidJoinDelay)
Cfg.WaveJoinDelay = (Cfg.WaveJoinDelay)
AccentOrder
WindowTints = {
Blue = { Background = , View = , Sidebar = , Titlebar = , Menu = },
Purple = { Background = , View = , Sidebar = , Titlebar = , Menu = },
Pink = { Background = , View = , Sidebar = , Titlebar = , Menu = },
Red = { Background = , View = , Sidebar = , Titlebar = , Menu = },
Orange = { Background = , View = , Sidebar = , Titlebar = , Menu = },
Yellow = { Background = , View = , Sidebar = , Titlebar = , Menu = },
Green = { Background = , View = , Sidebar = , Titlebar = , Menu = },
Graphite = { Background = , View = , Sidebar = , Titlebar = , Menu = },
}
accentName, tint (WindowTints)
(()
dark = Cascade.Accents[accentName].Dark
dark.Background = .(tint.Background)
dark.View = .(tint.View)
dark.Sidebar = .(tint.Sidebar)
dark.Titlebar = .(tint.Titlebar)
dark.MenuButton = { MenuBackground = .(tint.Menu) }
)
ExtraAccents = {
{ , , { Background = , View = , Sidebar = , Titlebar = , Menu = } },
{ , , { Background = , View = , Sidebar = , Titlebar = , Menu = } },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
{ , },
}
(name, hex, customBg)
main = .(hex)
h, s, val = main:()
selection = .(h, .(s * , ), .(val * , ))
focused = .(h, s, .(val * , ))
buttonBottom = .(h, .(s * , ), .(val * , ))
bgS = .(s * , )
()
{
SwitchAccent = main,
Selection = selection,
SelectionFocused = focused,
Toggle = {
SwitchOn = main,
SwitchOff = .(),
Fill = .()
},
Slider = {
Fill = main,
Empty = .()
},
Button = { FillPrimary = ColorSequence.(focused, buttonBottom) },
}
dark = ()
dark.Background = .(h, bgS, )
dark.View = .(h, bgS, )
dark.Sidebar = .(h, bgS * , )
dark.Titlebar = .(h, bgS * , )
dark.MenuButton = { MenuBackground = .(h, bgS * , ) }
customBg
dark.Background = .(customBg.Background)
dark.View = .(customBg.View)
dark.Sidebar = .(customBg.Sidebar)
dark.Titlebar = .(customBg.Titlebar)
dark.MenuButton = { MenuBackground = .(customBg.Menu) }
Cascade.Accents[name] = {
_id = name,
Dark = dark,
Light = (),
}
originalSet = ()
{
SwitchAccent = .(),
Selection = .(),
SelectionFocused = .(),
Toggle = { SwitchOn = .() },
Button = {
FillPrimary = ColorSequence.({
ColorSequenceKeypoint.(, .(, , )),
ColorSequenceKeypoint.(, .(, , )),
}),
},
}
darkSet = ()
darkSet.Background = .()
darkSet.View = .()
darkSet.Sidebar = .()
darkSet.Titlebar = .()
darkSet.MenuButton = { MenuBackground = .() }
Cascade.Accents.Dark = {
_id = ,
Dark = darkSet,
Light = (),
}
AccentOrder = { , , , , , , , , }
_, entry (ExtraAccents)
(MakeAccent, entry[], entry[], entry[])
Cascade.Accents[entry[]]
.(AccentOrder, entry[])
(mainHex, glowHex, darkHex)
main = .(mainHex)
glow = .(glowHex)
dark = .(darkHex)
bg = .()
view = .()
sidebar = .()
title = .()
menu = .()
(withBackground)
set = {
SwitchAccent = main,
Selection = dark,
SelectionFocused = glow,
Toggle = {
SwitchOn = main,
SwitchOff = .(),
Fill = .(),
},
Slider = {
Fill = main,
Empty = .(),
},
Button = {
FillPrimary = ColorSequence.({
ColorSequenceKeypoint.new(0, glow),
ColorSequenceKeypoint.new(0.5, main),
ColorSequenceKeypoint.new(1, dark),
}),
},
}
if withBackground then
set.Background = bg
set.View = view
set.Sidebar = sidebar
set.Titlebar = title
set.MenuButton = { MenuBackground = menu }
end
return set
end
return {
_id = "Crimson",
Dark = buildSet(true),
Light = buildSet(false),
}
end
local ultimateRed = DeepCrimson("#FF1E3C", "#FF4D66", "#8F0018")
Cascade.Accents.Red = ultimateRed
Cascade.Accents.Crimson = ultimateRed
Cascade.Accents.Blood = ultimateRed
if not table.find(AccentOrder, "Blood") then
table.insert(AccentOrder, "Blood")
end
end
end
local function StyleDropdown(btn)
pcall(function()
local indicator = btn.__instance:FindFirstChild("PullDownIndicator")
local img = indicator and indicator:FindFirstChild("Indicators")
if img then
img.Image = "rbxassetid://115187614425058"
img.ImageColor3 = Color3.fromRGB(255, 255, 255)
img.Size = UDim2.fromOffset(18, 18)
img.Position = UDim2.fromScale(0.5, 0.5)
img.AnchorPoint = Vector2.new(0.5, 0.5)
end
end)
end
local function CopyText(text)
pcall(function()
if setclipboard then setclipboard(text)
elseif toclipboard then toclipboard(text)
elseif syn and syn.write_clipboard then syn.write_clipboard(text)
elseif Clipboard and Clipboard.set then Clipboard.set(text) end
end)
end
local RedFlatTheme = {
Background = Color3.fromHex("#080001"),
View = Color3.fromHex("#0D0003"),
Sidebar = Color3.fromHex("#0A0002"),
Titlebar = Color3.fromHex("#1C0005"),
MenuButton = { MenuBackground = Color3.fromHex("#100003") },
}
local App = Cascade.New({
Name = "Smooth Hub AOTR",
WindowPill = true,
Theme = Cascade.Themes.Dark, -- GIỮ NGUYÊN DÒNG NÀY
Accent = (table.find(AccentOrder, SavedUi.Theme) and Cascade.Accents[SavedUi.Theme]) or Cascade.Accents[ConfigStore.DEFAULT_LOOK.Theme] or Cascade.Accents.Pink,
})
AppRef = App
pcall(function()
App.Accent = Cascade.Accents.Red
end)
local WindowSize = UDim2.fromOffset(850, 530)
pcall(function()
local cam = workspace.CurrentCamera
if cam then
local vp = cam.ViewportSize
WindowSize = UDim2.fromOffset(math.clamp(vp.X - 30, 320, 850), math.clamp(vp.Y - 30, 240, 530))
end
end)
local Window = App:Window({
Title = "Attack on Titan Revolution",
Subtitle = "Smooth Hub | Version 11.5",
Size = WindowSize,
Resizable = true,
Draggable = true,
UIBlur = true
})
UiSync.Defaults.UIBlur = false
UiSync.Defaults.Dropshadow = Window.Dropshadow
-- [smooth] Auto Hide Menu: hide right away from the saved setting, don't wait for the whole script to load
SH.MenuTouched = false
SH.MenuEarlyHidden = false
do
local hub = type(SavedData) == "table" and SavedData.Hub
if type(hub) == "table" and hub.AutoHideMenu == true then
local ok = pcall(function() Window.Minimized = true end)
SH.MenuEarlyHidden = ok
end
end
pcall(function() Window.Maximized = false end) -- เริ่มที่ขนาดปกติเสมอ (กันหน้าต่างเต็มจอค้างหลังรีโหลด)
task.delay(4, function() -- กันหน้าต่างค้างเต็มจอหลังโหลดธีมเสร็จ
pcall(function() if Window.Maximized then Window.Maximized = false end end)
end)
local MenuCategory = Cascade.Components.Section(Window, {
Title = "Menu",
Disclosure = false
})
local CharTab = MenuCategory:Tab({
Title = "Menu Lobby",
Icon = Icon("houseDoorFill", "houseFill", "personCircleFill"),
})
local StatusSection = CharTab:PageSection({
Title = "📋 Status",
Subtitle = IN_MENU and "You are in the menu place." or "Not in the menu place - menu features are idle."
})
local StatusForm = StatusSection:Form()
local StatusRow = StatusForm:Row()
StatusRow:Left():TitleStack({ Title = "Status", Subtitle = "What the script is doing right now." })
local StatusLabel = StatusRow:Right():Label({ Text = IN_MENU and "Idle" or "N/A" })
local function SetStatus(text)
pcall(function() StatusLabel.Text = tostring(text) end)
end
local SLOT_LETTERS = { "A", "B", "C" }
local SlotOptions = { "Slot A", "Slot B", "Slot C" }
local function TextOf(o)
local t = o.Text or ""
t = t:gsub("<[^>]->", "")
t = t:gsub("^%s+", ""):gsub("%s+$", "")
return t:upper()
end
local function IsTextObj(o)
return o:IsA("TextLabel") or o:IsA("TextButton")
end
local function IsShown(obj)
local cur = obj
while cur and cur ~= PlayerGui do
if cur:IsA("GuiObject") and not cur.Visible then return false end
if cur:IsA("ScreenGui") and not cur.Enabled then return false end
cur = cur.Parent
end
return obj:IsA("GuiObject") and obj.AbsoluteSize.X > 0 and obj.AbsoluteSize.Y > 0
end
local function ToButton(o)
if o:IsA("GuiButton") then return o end
local cur = o.Parent
for _ = 1, 3 do
if not cur then break end
if cur:IsA("GuiButton") then return cur end
cur = cur.Parent
end
local p = o.Parent
if p then return p:FindFirstChildWhichIsA("GuiButton", true) end
return nil
end
local function FindSelectIn(container)
for _, d in ipairs(container:GetDescendants()) do
if IsTextObj(d) and TextOf(d) == "SELECT" and IsShown(d) then
local b = ToButton(d)
if b then return b end
end
end
return nil
end
local function FindSlotButton(letter)
local want = "SLOT " .. letter
for _, d in ipairs(PlayerGui:GetDescendants()) do
if IsTextObj(d) and TextOf(d) == want and IsShown(d) then
local cur = d.Parent
for _ = 1, 6 do
if not cur or cur == PlayerGui then break end
local btn = FindSelectIn(cur)
if btn then return btn end
cur = cur.Parent
end
end
end
return nil
end
local function SlotScreenVisible()
for _, d in ipairs(PlayerGui:GetDescendants()) do
if IsTextObj(d) and TextOf(d) == "SLOT A" and IsShown(d) then return true end
end
return false
end
local function ActivateButton(btn)
pcall(function() GuiService.SelectedObject = btn end)
task.wait(0.15)
pcall(function()
VirtualInputManager:SendKeyEvent(true, Enum.KeyCode.Return, false, game)
task.wait(0.05)
VirtualInputManager:SendKeyEvent(false, Enum.KeyCode.Return, false, game)
end)
task.wait(1.0)
if btn.Parent and IsShown(btn) then
pcall(function()
if type(firesignal) == "function" then
firesignal(btn.MouseButton1Click)
firesignal(btn.Activated)
end
end)
end
pcall(function()
if GuiService.SelectedObject == btn then GuiService.SelectedObject = nil end
end)
end
local selecting = false
local function SelectCharacter()
if selecting then return false end
if not IN_MENU then SetStatus("N/A") return false end
selecting = true
local letter = SLOT_LETTERS[Cfg.Slot] or "A"
local btn = FindSlotButton(letter)
local result = false
if btn then
SetStatus("Selecting Slot " .. letter .. "...")
ActivateButton(btn)
SetStatus("Selected Slot " .. letter)
result = true
else
SetStatus("Slot " .. letter .. " UI not found")
end
selecting = false
return result
end
local CharSection = CharTab:PageSection({
Title = "🎭 Character Selection",
Subtitle = "Pick one slot. The script presses its SELECT button for you."
})
local CharForm = CharSection:Form()
local SlotRow = CharForm:Row()
SlotRow:Left():TitleStack({ Title = "Slot", Subtitle = "Choose which character slot to use (one at a time)." })
UiSync.Slot = SlotRow:Right():PullDownButton({
Label = SlotOptions[Cfg.Slot],
Options = SlotOptions,
Value = Cfg.Slot,
ValueChanged = function(self, index)
if type(index) == "table" then index = index[1] end
if type(index) ~= "number" or not SlotOptions[index] then return end
Cfg.Slot = index
self.Label = SlotOptions[index]
Save()
end
})
UiSync.Add(function()
local i = SlotOptions[Cfg.Slot] and Cfg.Slot or 1
UiSync.Slot.Value = i
UiSync.Slot.Label = SlotOptions[i]
end)
local AutoSelRow = CharForm:Row()
AutoSelRow:Left():TitleStack({
Title = "Select Character",
Subtitle = "Automatically press SELECT on the chosen slot when the screen shows up."
})
UiSync.Link(AutoSelRow:Right():Toggle({
Value = Cfg.AutoSelect,
ValueChanged = function(self, value)
Cfg.AutoSelect = value and true or false
Save()
end
}), Cfg, "AutoSelect")
local SelNowRow = CharForm:Row()
SelNowRow:Left():TitleStack({ Title = "Select Now", Subtitle = "Press SELECT on the chosen slot once." })
local SelNowBtn = SelNowRow:Right():Button({
Label = "Select",
Callback = function() end
})
BindButton(SelNowBtn, SelectCharacter)
local function GetLobbyRemote()
local ok, remote = pcall(function()
local assets = ReplicatedStorage:WaitForChild("Assets", 5)
local remotes = assets and assets:WaitForChild("Remotes", 5)
return remotes and remotes:WaitForChild("GET", 5)
end)
return ok and remote or nil
end
local cachedRemote = nil
local lastRequestAt = 0
local function JoinLobby()
if not IN_MENU then SetStatus("N/A") return end
if os.clock() - lastRequestAt < 3 then return end
lastRequestAt = os.clock()
task.spawn(function()
local remote = cachedRemote
if not remote or not remote.Parent then
remote = GetLobbyRemote()
cachedRemote = remote
end
if not remote then SetStatus("Lobby remote not found") return end
SetStatus("Joining lobby...")
local ok, err = pcall(function()
return remote:InvokeServer("Functions", "Teleport", "Lobby", nil)
end)
SetStatus(ok and "Join request sent" or ("Join failed: " .. tostring(err)))
end)
end
local JoinSection = CharTab:PageSection({
Title = "🚪 Auto Join Lobby",
Subtitle = "Repeatedly requests the lobby. Delay is " .. MIN_DELAY .. " - " .. MAX_DELAY .. " seconds."
})
local JoinForm = JoinSection:Form()
local AutoJoinRow = JoinForm:Row()
AutoJoinRow:Left():TitleStack({
Title = "Auto Join Lobby",
Subtitle = "Send the join-lobby request every Join Delay seconds."
})
UiSync.Link(AutoJoinRow:Right():Toggle({
Value = Cfg.AutoJoin,
ValueChanged = function(self, value)
Cfg.AutoJoin = value and true or false
Save()
end
}), Cfg, "AutoJoin")
local DelayRow = JoinForm:Row()
DelayRow:Left():TitleStack({
Title = "Join Delay",
Subtitle = "Seconds between requests (" .. MIN_DELAY .. " - " .. MAX_DELAY .. "). Minimum is " .. MIN_DELAY .. "s so it can't spam by accident."
})
local DelayRight = DelayRow:Right()
local DelayLabel = DelayRight:Label({ Text = Cfg.JoinDelay .. "s" })
UiSync.Link(DelayRight:Slider({
Minimum = MIN_DELAY,
Maximum = MAX_DELAY,
Value = Cfg.JoinDelay,
ValueChanged = function(self, value)
Cfg.JoinDelay = ClampDelay(value)
pcall(function() DelayLabel.Text = Cfg.JoinDelay .. "s" end)
Save()
end
}), Cfg, "JoinDelay", function(_, want)
pcall(function() DelayLabel.Text = want .. "s" end)
end)
local CountdownRow = JoinForm:Row()
CountdownRow:Left():TitleStack({
Title = "Next Join",
Subtitle = "Countdown until the next join-lobby request."
})
local CountdownLabel = CountdownRow:Right():Label({ Text = "Off" })
local lastCountdownText = nil
local function SetCountdown(text)
if text == lastCountdownText then return end
lastCountdownText = text
pcall(function() CountdownLabel.Text = text end)
end
local JoinNowRow = JoinForm:Row()
JoinNowRow:Left():TitleStack({ Title = "Join Now", Subtitle = "Send the join-lobby request once." })
local JoinNowBtn = JoinNowRow:Right():Button({
Label = "Join",
Callback = function() end
})
BindButton(JoinNowBtn, JoinLobby)
do
local RANK = { COMMON = 1, UNCOMMON = 2, RARE = 3, EPIC = 4, LEGENDARY = 5, MYTHIC = 6, SECRET = 7, EXCLUSIVE = 8 }
local STOP_OPTIONS = { "Rare", "Epic", "Legendary", "Mythic", "Off" }
if not table.find(STOP_OPTIONS, Cfg.SpinStopRarity) then Cfg.SpinStopRarity = "Epic" end
local KEEP_ACTIONS = { "Stop", "Store", "Ask" }
local KEEP_LABELS = { "Stop spinning", "Store & keep spinning", "Ask me" }
if not table.find(KEEP_ACTIONS, Cfg.SpinOnKeep) then Cfg.SpinOnKeep = "Stop" end
local FAMILY_RARITY = {
HELOS = "MYTHIC", FRITZ = "MYTHIC",
YEAGER = "LEGENDARY", ACKERMAN = "LEGENDARY", REISS = "LEGENDARY",
GALLIARD = "EPIC", ZOE = "EPIC", LEONHART = "EPIC", TYBUR = "EPIC",
KSAVER = "EPIC", BRAUN = "EPIC", FINGER = "EPIC", ARLERT = "EPIC",
BRAUS = "RARE", KRUGER = "RARE", AZUMABITO = "RARE", SMITH = "RARE",
GRICE = "RARE", SPRINGER = "RARE", KIRSTEIN = "RARE",
}
local function Cap(s)
return (tostring(s):lower():gsub("^%l", string.upper))
end
local function Pretty(name)
return (tostring(name):lower():gsub("(%a)([%w_']*)", function(a, b) return a:upper() .. b end))
end
local FAMILY_LIST, FAMILY_LABELS = {}, {}
for key, rar in pairs(FAMILY_RARITY) do FAMILY_LIST[#FAMILY_LIST + 1] = { key = key, rarity = rar } end
table.sort(FAMILY_LIST, function(a, b)
if RANK[a.rarity] ~= RANK[b.rarity] then return RANK[a.rarity] > RANK[b.rarity] end
return a.key < b.key
end)
for i, f in ipairs(FAMILY_LIST) do FAMILY_LABELS[i] = Pretty(f.key) .. " (" .. Cap(f.rarity) .. ")" end
local rulesSrc, rulesCache = nil, {}
local function Rules()
if rulesSrc ~= Cfg.SpinFamilyRules then
rulesSrc = Cfg.SpinFamilyRules
rulesCache = {}
for key, rule in tostring(rulesSrc):gmatch("([^=;]+)=(%a+)") do
if rule == "Keep" or rule == "Skip" or rule == "Ask" then rulesCache[key:upper()] = rule end
end
end
return rulesCache
end
local function WriteRules(map)
local parts = {}
for k, v in pairs(map) do parts[#parts + 1] = k .. "=" .. v end
table.sort(parts)
Cfg.SpinFamilyRules = table.concat(parts, ";")
Save()
end
local function SetRule(name, rule)
local copy = {}
for k, v in pairs(Rules()) do copy[k] = v end
copy[tostring(name):upper()] = rule
WriteRules(copy)
end
local function RulesSummary()
local keep, skip, ask = {}, {}, {}
for key, rule in pairs(Rules()) do
local list = (rule == "Keep" and keep) or (rule == "Skip" and skip) or ask
list[#list + 1] = Pretty(key)
end
local function J(l)
table.sort(l)
return #l > 0 and table.concat(l, ", ") or "-"
end
return ("Always keep: %s | Never keep: %s | Always ask: %s"):format(J(keep), J(skip), J(ask))
end
local function Decide(name, rarity)
local rule = Rules()[tostring(name):upper()]
if rule == "Skip" then return "roll" end
if rule == "Ask" then return "ask" end
local stopRank = Cfg.SpinStopRarity == "Off" and math.huge or (RANK[Cfg.SpinStopRarity:upper()] or RANK.EPIC)
local keeper = rule == "Keep" or (RANK[tostring(rarity):upper()] or 0) >= stopRank
if not keeper then return "roll" end
if Cfg.SpinOnKeep == "Store" then return "store" end
if Cfg.SpinOnKeep == "Ask" then return "ask" end
return "stop"
end
SH.Spin = SH.Spin or {}
SH.Spin.Decide = Decide
SH.Spin.SetRule = SetRule
local SpinForm = CharTab:PageSection({
Title = "🎰 Roll Family",
Subtitle = "Auto roll your family."
}):Form()
local RuleForm = CharTab:PageSection({
Title = "⚙️ Settings Roll",
Subtitle = "Choose which families to keep."
}):Form()
local ShowForm = CharTab:PageSection({
Title = "📜 Show Roll",
Subtitle = "Your roll history."
}):Form()
local spinToggle
local spinRow = SpinForm:Row()
spinRow:Left():TitleStack({
Title = "Auto Spin [ Lucky999+ ]",
Subtitle = "Keeps pressing ROLL for you."
})
spinToggle = UiSync.Link(spinRow:Right():Toggle({
Value = Cfg.AutoSpin,
ValueChanged = function(self, value)
Cfg.AutoSpin = value and true or false
Save()
end
}), Cfg, "AutoSpin")
local statusRow = SpinForm:Row()
statusRow:Left():TitleStack({ Title = "Spin Status", Subtitle = "What Auto Spin is doing now." })
local statusLabel = statusRow:Right():Label({ Text = "Off" })
local lastStatus = nil
local function Status(text)
text = tostring(text)
if text == lastStatus then return end
lastStatus = text
pcall(function() statusLabel.Text = text end)
end
local lastRow = SpinForm:Row()
local lastStack = lastRow:Left():TitleStack({ Title = "Last Roll", Subtitle = "No rolls yet." })
local lastLabel = lastRow:Right():Label({ Text = "-" })
local stopRow = RuleForm:Row()
stopRow:Left():TitleStack({
Title = "Keep Rarity",
Subtitle = "Keep this rarity or higher. Off = never stop for rarity."
})
local function StopIndex()
return table.find(STOP_OPTIONS, Cfg.SpinStopRarity) or 2
end
local stopDrop = stopRow:Right():PullDownButton({
Label = STOP_OPTIONS[StopIndex()],
Options = STOP_OPTIONS,
Value = StopIndex(),
ValueChanged = function(self, index)
if type(index) == "table" then index = index[1] end
if type(index) ~= "number" or not STOP_OPTIONS[index] then return end
Cfg.SpinStopRarity = STOP_OPTIONS[index]
self.Label = STOP_OPTIONS[index]
Save()
end
})
UiSync.Add(function()
local i = StopIndex()
stopDrop.Value = i
stopDrop.Label = STOP_OPTIONS[i]
end)
local keepRow = RuleForm:Row()
keepRow:Left():TitleStack({
Title = "When I Get A Keeper",
Subtitle = "Stop, store it and keep rolling, or ask me."
})
local function KeepIndex()
return table.find(KEEP_ACTIONS, Cfg.SpinOnKeep) or 1
end
local keepDrop = keepRow:Right():PullDownButton({
Label = KEEP_LABELS[KeepIndex()],
Options = KEEP_LABELS,
Value = KeepIndex(),
ValueChanged = function(self, index)
if type(index) == "table" then index = index[1] end
if type(index) ~= "number" or not KEEP_ACTIONS[index] then return end
Cfg.SpinOnKeep = KEEP_ACTIONS[index]
self.Label = KEEP_LABELS[index]
Save()
end
})
local rulePickers, syncingRules, rulesStack = {}, false, nil
local function PickedIndexes(rule)
local picked = {}
for i, f in ipairs(FAMILY_LIST) do
if Rules()[f.key] == rule then picked[#picked + 1] = i end
end
return picked
end
local function RefreshRulePickers()
if rulesStack then pcall(function() rulesStack.Subtitle = RulesSummary() end) end
syncingRules = true
for rule, p in pairs(rulePickers) do
if p.multi then pcall(function() p.btn.Value = PickedIndexes(rule) end) end
end
syncingRules = false
end
local function ApplyPicked(rule, indexes)
local picked = {}
for _, i in ipairs(SettingsStore.ValidIndexes(indexes, #FAMILY_LIST)) do picked[FAMILY_LIST[i].key] = true end
local copy = {}
for k, v in pairs(Rules()) do copy[k] = v end
for _, f in ipairs(FAMILY_LIST) do
if picked[f.key] then
copy[f.key] = rule
elseif copy[f.key] == rule then
copy[f.key] = nil
end
end
WriteRules(copy)
RefreshRulePickers()
end
local function AddRulePicker(title, subtitle, rule)
local row = RuleForm:Row()
row:Left():TitleStack({ Title = title, Subtitle = subtitle })
local ok, btn = pcall(function()
return row:Right():PopUpButton({
Options = FAMILY_LABELS,
Maximum = #FAMILY_LABELS,
Value = PickedIndexes(rule),
ValueChanged = function(self, value)
if syncingRules then return end
ApplyPicked(rule, type(value) == "table" and value or { value })
end
})
end)
if ok and btn then
rulePickers[rule] = { btn = btn, multi = true }
else
btn = row:Right():PullDownButton({
Label = "Tap a family to add / remove",
Options = FAMILY_LABELS,
Value = 1,
ValueChanged = function(self, index)
if syncingRules then return end
if type(index) == "table" then index = index[1] end
if type(index) ~= "number" or not FAMILY_LIST[index] then return end
local picked = PickedIndexes(rule)
local at = table.find(picked, index)
if at then table.remove(picked, at) else picked[#picked + 1] = index end
ApplyPicked(rule, picked)
self.Label = (at and "Removed " or "Added ") .. FAMILY_LABELS[index]
end
})
rulePickers[rule] = { btn = btn, multi = false }
end
StyleDropdown(btn)
end
AddRulePicker("Always Keep Families",
"Always keep these, even below Keep Rarity.", "Keep")
AddRulePicker("Always Ask Families",
"Ask me every time these roll.", "Ask")
AddRulePicker("Never Keep Families",
"Never keep these, even above Keep Rarity.", "Skip")
local rulesRow = RuleForm:Row()
rulesStack = rulesRow:Left():TitleStack({ Title = "Saved Rules", Subtitle = RulesSummary() })
UiSync.Add(function()
local i = KeepIndex()
keepDrop.Value = i
keepDrop.Label = KEEP_LABELS[i]
RefreshRulePickers()
end)
local useDelayRow = SpinForm:Row()
useDelayRow:Left():TitleStack({
Title = "Use Spin Delay",
Subtitle = "On: wait after every roll. Off: go on as soon as the result shows."
})
UiSync.Link(useDelayRow:Right():Toggle({
Value = Cfg.SpinUseDelay ~= false,
ValueChanged = function(self, value)
Cfg.SpinUseDelay = value and true or false
Save()
end
}), Cfg, "SpinUseDelay")
local delayRow = SpinForm:Row()
delayRow:Left():TitleStack({
Title = "Spin Delay",
Subtitle = "Seconds to wait after each roll (0.5 - 10)."
})
local delayRight = delayRow:Right()
local delayLabel = delayRight:Label({ Text = Cfg.SpinDelay .. "s" })
UiSync.Link(delayRight:Slider({
Minimum = 0.5,
Maximum = 10,
Value = Cfg.SpinDelay,
ValueChanged = function(self, value)
Cfg.SpinDelay = math.clamp(math.floor((tonumber(value) or 3) * 2 + 0.5) / 2, 0.5, 10)
pcall(function() delayLabel.Text = Cfg.SpinDelay .. "s" end)
Save()
end
}), Cfg, "SpinDelay", function(_, want)
pcall(function() delayLabel.Text = want .. "s" end)
end)
local history, stats = {}, { rolls = 0, byRarity = {} }
SH.Spin.History = history
local RARITY_ORDER = { "EXCLUSIVE", "SECRET", "MYTHIC", "LEGENDARY", "EPIC", "UNCOMMON", "RARE", "COMMON" }
local RefreshList = function() end
local showListRow = ShowForm:Row()
showListRow:Left():TitleStack({
Title = "Show Rolled Families",
Subtitle = "Show or hide the list."
})
UiSync.Link(showListRow:Right():Toggle({
Value = Cfg.SpinShowList == true,
ValueChanged = function(self, value)
Cfg.SpinShowList = value and true or false
Save()
RefreshList()
end
}), Cfg, "SpinShowList", function() RefreshList() end)
local sizeRow = ShowForm:Row()
sizeRow:Left():TitleStack({
Title = "List Size",
Subtitle = "Recent rolls to show (5 - 100)."
})
local sizeRight = sizeRow:Right()
local sizeLabel = sizeRight:Label({ Text = tostring(Cfg.SpinListSize) })
UiSync.Link(sizeRight:Slider({
Minimum = 5,
Maximum = 100,
Value = Cfg.SpinListSize,
ValueChanged = function(self, value)
Cfg.SpinListSize = math.clamp(math.floor((tonumber(value) or 15) + 0.5), 5, 100)
pcall(function() sizeLabel.Text = tostring(Cfg.SpinListSize) end)
Save()
RefreshList()
end
}), Cfg, "SpinListSize", function(_, want)
pcall(function() sizeLabel.Text = tostring(want) end)
RefreshList()
end)
local listRow = ShowForm:Row()
local listStack = listRow:Left():TitleStack({ Title = "Rolled Families", Subtitle = "Nothing rolled yet." })
local function ListText()
local lines = {}
for i = #history, math.max(1, #history - (math.clamp(tonumber(Cfg.SpinListSize) or 15, 5, 100) - 1)), -1 do
local e = history[i]
lines[#lines + 1] = (e.n == 0 and "Start" or ("#" .. e.n)) .. " " .. e.name .. " (" .. e.rarity .. ")"
end
return #lines > 0 and table.concat(lines, "\n") or "Nothing rolled yet."
end
local function SummaryText()
local parts = {}
for _, r in ipairs(RARITY_ORDER) do
local n = stats.byRarity[r]
if n and n > 0 then parts[#parts + 1] = Cap(r) .. " " .. n end
end
return ("%d rolls%s"):format(stats.rolls, #parts > 0 and (" | " .. table.concat(parts, " · ")) or "")
end
RefreshList = function()
pcall(function()
if Cfg.SpinShowList then
listStack.Subtitle = ListText()
elseif #history > 0 then
listStack.Subtitle = "Hidden | " .. SummaryText()
else
listStack.Subtitle = "Hidden. Turn on 'Show Rolled Families'."
end
end)
end
RefreshList()
local function LogResult(name, rarity, isStart)
if not isStart then
stats.rolls += 1
stats.byRarity[rarity] = (stats.byRarity[rarity] or 0) + 1
end
local stamp = ""
pcall(function() stamp = os.date("%H:%M:%S") end)
local entry = { n = isStart and 0 or stats.rolls, name = Pretty(name), rarity = Cap(rarity), time = stamp }
history[#history + 1] = entry
if #history > 300 then table.remove(history, 1) end
SH.dbg(("[SmoothHub] Spin %s: %s (%s)"):format(isStart and "(start)" or ("#" .. entry.n), entry.name, entry.rarity))
pcall(function()
lastLabel.Text = entry.name .. " (" .. entry.rarity .. ")"
lastStack.Subtitle = (isStart and "Current family when you started" or ("Roll #" .. entry.n)) .. " | " .. SummaryText()
end)
RefreshList()
end
local function HistoryText()
local lines = {}
for _, e in ipairs(history) do
lines[#lines + 1] = ("%s %s %s (%s)"):format(e.n == 0 and "start" or ("#" .. e.n), e.time, e.name, e.rarity)
end
lines[#lines + 1] = "--- " .. SummaryText()
return table.concat(lines, "\n")
end
function SH.Spin.PrintHistory() SH.dbg(HistoryText()) end
function SH.Spin.Clear()
for i = #history, 1, -1 do history[i] = nil end
stats.rolls, stats.byRarity = 0, {}
pcall(function() lastLabel.Text = "-" lastStack.Subtitle = "No rolls yet." end)
RefreshList()
end
local copyRow = ShowForm:Row()
copyRow:Left():TitleStack({ Title = "Spin Log", Subtitle = "Copy all rolls to the clipboard." })
local copyBtn = copyRow:Right():Button({ Label = "Copy", Callback = function() end })
BindButton(copyBtn, function()
local text = HistoryText()
pcall(function()
if type(setclipboard) == "function" then setclipboard(text) elseif type(toclipboard) == "function" then toclipboard(text) end
end)
SH.dbg(text)
end)
local rollObj, rollScanAt = nil, 0
local function FindRoll()
if rollObj and rollObj.Parent and IsShown(rollObj) then return rollObj end
rollObj = nil
if os.clock() - rollScanAt < 1 then return nil end
rollScanAt = os.clock()
local n = 0
for _, d in ipairs(PlayerGui:GetDescendants()) do
n += 1
if n % 800 == 0 then task.wait() end
if IsTextObj(d) and TextOf(d):match("^ROLL") and IsShown(d) then
rollObj = d
return d
end
end
return nil
end
local function RollCount(obj)
return tonumber(TextOf(obj):match("%((%d+)%)"))
end
local famObj, famScanAt = nil, 0
local function ParseFamily(o)
local name, rarity = TextOf(o):match("^(.-)%s*%((%a+)%)$")
if name and RANK[rarity] then return name, rarity end
return nil
end
local function ReadFamily()
if famObj and famObj.Parent and IsShown(famObj) then
local n, r = ParseFamily(famObj)
if n then return n, r end
end
famObj = nil
if os.clock() - famScanAt < 0.4 then return nil end
famScanAt = os.clock()
local n = 0
for _, d in ipairs(PlayerGui:GetDescendants()) do
n += 1
if n % 800 == 0 then task.wait() end
if IsTextObj(d) and IsShown(d) then
local name, rarity = ParseFamily(d)
if name then
famObj = d
return name, rarity
end
end
end
return nil
end
local function WaitForNewFamily(nameBefore, rarityBefore, maxWait)
local t0 = os.clock()
local seenKey, seenAt = nil, 0
while os.clock() - t0 < maxWait do
if not Alive() or not Cfg.AutoSpin then return false end
local n, r = ReadFamily()
if n and (n ~= nameBefore or r ~= rarityBefore) then
local key = n .. "|" .. r
if key ~= seenKey then
seenKey, seenAt = key, os.clock()
elseif os.clock() - seenAt >= 0.3 then
return true
end
else
seenKey = nil
end
task.wait(0.05)
end
return false
end
task.spawn(function()
local lastKey, scanAt, hinted = nil, 0, false
while Alive() do
task.wait(0.15)
local n, r
if famObj and famObj.Parent and IsShown(famObj) then
n, r = ParseFamily(famObj)
elseif IN_MENU and os.clock() - scanAt >= 3 then
scanAt = os.clock()
n, r = ReadFamily()
end
if n then
hinted = false
local key = n .. "|" .. r
if key ~= lastKey then
lastKey = key
pcall(function()
lastLabel.Text = Pretty(n) .. " (" .. Cap(r) .. ")"
lastStack.Subtitle = "Live - your current family | " .. SummaryText()
end)
end
elseif not hinted and lastKey == nil and #history == 0 then
hinted = true
pcall(function() lastStack.Subtitle = "Open the Family page to see your current family live." end)
end
end
end)
local pressPref = 1
local PRESS_METHODS = {
{ name = "Enter", run = function(btn)
pcall(function() GuiService.SelectedObject = btn end)
task.wait(0.1)
pcall(function()
VirtualInputManager:SendKeyEvent(true, Enum.KeyCode.Return, false, game)
task.wait(0.05)
VirtualInputManager:SendKeyEvent(false, Enum.KeyCode.Return, false, game)
end)
return true
end },
{ name = "Activated signal", run = function(btn)
if type(firesignal) ~= "function" then return false end
return (pcall(firesignal, btn.Activated))
end },
{ name = "Click signal", run = function(btn)
if type(firesignal) ~= "function" then return false end
return (pcall(firesignal, btn.MouseButton1Click))
end },
{ name = "Mouse click", run = function(btn)
local gui = btn:FindFirstAncestorOfClass("ScreenGui")
local inset = (gui and gui.IgnoreGuiInset) and Vector2.zero or GuiService:GetGuiInset()
local pos = btn.AbsolutePosition + btn.AbsoluteSize / 2 + inset
return (pcall(function()
VirtualInputManager:SendMouseButtonEvent(pos.X, pos.Y, 0, true, game, 0)
task.wait(0.05)
VirtualInputManager:SendMouseButtonEvent(pos.X, pos.Y, 0, false, game, 0)
end))
end },
{ name = "Gamepad A", run = function(btn)
pcall(function() GuiService.SelectedObject = btn end)
task.wait(0.1)
return (pcall(function()
VirtualInputManager:SendKeyEvent(true, Enum.KeyCode.ButtonA, false, game)
task.wait(0.05)
VirtualInputManager:SendKeyEvent(false, Enum.KeyCode.ButtonA, false, game)
end))
end },
}
local function PressRoll(obj, attempt)
local btn = ToButton(obj)
if not btn then return nil end
local count = #PRESS_METHODS
for step = 0, count - 1 do
local idx = ((pressPref - 1 + (attempt or 0) + step) % count) + 1
local m = PRESS_METHODS[idx]
if m.run(btn) then return idx, m.name end
end
return nil
end
local function WaitCountDrop(obj, before, maxWait, nameBefore, rarityBefore)
local t0 = os.clock()
while os.clock() - t0 < maxWait do
if not (obj.Parent) then return true end
local now = RollCount(obj)
if now ~= nil and now < before then return true end
if nameBefore and famObj and famObj.Parent then
local n, r = ParseFamily(famObj)
if n and (n ~= nameBefore or r ~= rarityBefore) then return true end
end
task.wait(0.1)
end
return false
end
local function PressButton(btn)
pcall(function() GuiService.SelectedObject = btn end)
task.wait(0.1)
pcall(function()
VirtualInputManager:SendKeyEvent(true, Enum.KeyCode.Return, false, game)
task.wait(0.05)
VirtualInputManager:SendKeyEvent(false, Enum.KeyCode.Return, false, game)
end)
end
local function FindShownButton(match)
local n = 0
for _, d in ipairs(PlayerGui:GetDescendants()) do
n += 1
if n % 800 == 0 then task.wait() end
if d:IsA("GuiButton") and IsShown(d) and match(d) then return d end
end
return nil
end
local function FindBottomIcon()
local cam = workspace.CurrentCamera
local vp = cam and cam.ViewportSize or Vector2.new(1920, 1080)
local cands, n = {}, 0
for _, d in ipairs(PlayerGui:GetDescendants()) do
n += 1
if n % 800 == 0 then task.wait() end
if d:IsA("GuiButton") and IsShown(d) then
local s = d.AbsoluteSize
if s.X >= 40 and s.X <= 220 and s.Y >= 40 and s.Y <= 220 and math.abs(s.X / s.Y - 1) <= 0.2 then
local c = d.AbsolutePosition + s / 2
if c.X < vp.X * 0.3 then cands[#cands + 1] = { btn = d, y = c.Y } end
end
end
end
if #cands < 4 then return nil end
table.sort(cands, function(a, b) return a.y > b.y end)
return cands[1].btn
end
local function ClickableFor(o)
if o:IsA("GuiButton") then return o end
local cur = o.Parent
for _ = 1, 3 do
if not cur or cur == PlayerGui then break end
if cur:IsA("GuiButton") then return cur end
cur = cur.Parent
end
local c = o.AbsolutePosition + o.AbsoluteSize / 2
local best, bestArea = nil, math.huge
for _, d in ipairs(PlayerGui:GetDescendants()) do
if d:IsA("GuiButton") and IsShown(d) then
local pos, size = d.AbsolutePosition, d.AbsoluteSize
if c.X >= pos.X and c.X <= pos.X + size.X and c.Y >= pos.Y and c.Y <= pos.Y + size.Y then
local area = size.X * size.Y
if area < bestArea then best, bestArea = d, area end
end
end
end
return best
end
local function FindMainMenuCustomize()
local custObj, hasPlay, hasCodes = nil, false, false
local n = 0
for _, d in ipairs(PlayerGui:GetDescendants()) do
n += 1
if n % 800 == 0 then task.wait() end
if IsTextObj(d) and IsShown(d) then
local t = TextOf(d)
if t == "CUSTOMIZATION" then custObj = d
elseif t == "PLAY" then hasPlay = true
elseif t == "CODES" then hasCodes = true end
end
end
if custObj and hasPlay and hasCodes then return ClickableFor(custObj) end
return nil
end
local navAt, navTries = 0, 0
local function OpenFamilyPage()
if os.clock() - navAt < 2.5 then return end
navAt = os.clock()
if navTries >= 8 then
Status("Couldn't open the Family page - open it once yourself")
return
end
local famBtn = FindShownButton(function(d)
if not d.Name:lower():find("family", 1, true) then return false end
local t = d:IsA("TextButton") and TextOf(d) or ""
return not t:find("X2", 1, true) and t ~= "BACK"
end)
if famBtn then
navTries += 1
Status("Opening the Family page...")
PressButton(famBtn)
return
end
local iconBtn = FindBottomIcon()
if iconBtn then
navTries += 1
Status("Opening the Family page (F icon)...")
PressButton(iconBtn)
return
end
local custBtn = FindMainMenuCustomize()
if custBtn then
navTries += 1
Status("Opening Customization...")
PressButton(custBtn)
return
end
Status("Open the Family page (menu buttons not found)")
end
SH.Spin = SH.Spin or {}
function SH.Spin.DumpButtons()
local lines = { "=== visible buttons (path | class | text | image) ===" }
for _, d in ipairs(PlayerGui:GetDescendants()) do
if d:IsA("GuiButton") and IsShown(d) then
local path = d:GetFullName():gsub("^Players%.[^%.]+%.PlayerGui%.", "")
local t = d:IsA("TextButton") and (d.Text or "") or ""
local img = d:IsA("ImageButton") and tostring(d.Image) or ""
lines[#lines + 1] = path .. " | " .. d.ClassName .. " | " .. t .. " | " .. img
end
end
local text = table.concat(lines, "\n")
pcall(function()
if type(setclipboard) == "function" then setclipboard(text) elseif type(toclipboard) == "function" then toclipboard(text) end
end)
pcall(function() if type(writefile) == "function" then writefile("SmoothHub_buttons.txt", text) end end)
SH.dbg(text)
return #lines - 1
end
local function Notify(text)
pcall(function()
game:GetService("StarterGui"):SetCore("SendNotification", {
Title = "Smooth Hub - Auto Spin", Text = text, Duration = 8,
})
end)
end
local getRemote = nil
local function InvokeGet(...)
if not getRemote or not getRemote.Parent then getRemote = GetLobbyRemote() end
if not getRemote then return false, "remote not found" end
local remote, args = getRemote, table.pack(...)
local done, ok, res = false, false, nil
task.spawn(function()
ok, res = pcall(function() return remote:InvokeServer(table.unpack(args, 1, args.n)) end)
done = true
end)
local t0 = os.clock()
while not done and os.clock() - t0 < 8 do task.wait(0.05) end
if not done then return false, "timeout" end
return ok, res
end
local function StoreCurrent(name, rarity)
Status(("Storing %s..."):format(Pretty(name)))
local ok, res = InvokeGet("Family", "Store")
SH.dbg("[SmoothHub] Auto Spin: Store ->", ok, res)
if not ok then return false, tostring(res) end
local t0 = os.clock()
while os.clock() - t0 < 4 do
task.wait(0.4)
if not Alive() or not Cfg.AutoSpin then return false, "stopped" end
local n2, r2 = ReadFamily()
if n2 == nil or n2 ~= name or r2 ~= rarity then return true end
end
return false, "your family didn't change after Store"
end
local function AskUser(name, rarity)
local gui = Instance.new("ScreenGui")
gui.Name = "SmoothHubSpinAsk"
gui.ResetOnSpawn = false
gui.DisplayOrder = 10000
gui.IgnoreGuiInset = true
local parented = false
for _, getParent in ipairs({
function() return type(gethui) == "function" and gethui() or nil end,
function() return game:GetService("CoreGui") end,
function() return PlayerGui end,
}) do
local okP, parent = pcall(getParent)
if okP and parent and pcall(function() gui.Parent = parent end) and gui.Parent then
parented = true
break
end
end
if not parented then gui:Destroy() return nil end
local frame = Instance.new("Frame")
frame.AnchorPoint = Vector2.new(0.5, 0.5)
frame.Position = UDim2.fromScale(0.5, 0.35)
frame.Size = UDim2.fromOffset(400, 200)
frame.BackgroundColor3 = Color3.fromRGB(24, 24, 28)
frame.Parent = gui
local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 12) corner.Parent = frame
local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(70, 70, 80) stroke.Thickness = 1 stroke.Parent = frame
local function Label(text, y, h, size, color, bold)
local l = Instance.new("TextLabel")
l.BackgroundTransparency = 1
l.Position = UDim2.fromOffset(16, y)
l.Size = UDim2.new(1, -32, 0, h)
l.Font = bold and Enum.Font.GothamBold or Enum.Font.Gotham
l.TextSize = size
l.TextColor3 = color
l.Text = text
l.TextXAlignment = Enum.TextXAlignment.Left
l.Parent = frame
return l
end
Label("You rolled a family", 14, 20, 14, Color3.fromRGB(160, 160, 170), false)
Label(Pretty(name) .. " - " .. Cap(rarity), 36, 34, 24, Color3.fromRGB(255, 255, 255), true)
Label("Do you want to keep it?", 76, 20, 14, Color3.fromRGB(200, 200, 210), false)
local choice = nil
local function Button(text, x, w, color, value)
local b = Instance.new("TextButton")
b.Position = UDim2.fromOffset(x, 118)
b.Size = UDim2.fromOffset(w, 52)
b.BackgroundColor3 = color
b.AutoButtonColor = true
b.Font = Enum.Font.GothamBold
b.TextSize = 13
b.TextColor3 = Color3.fromRGB(255, 255, 255)
b.TextWrapped = true
b.Text = text
b.Parent = frame
local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, 8) c.Parent = b
b.MouseButton1Click:Connect(function() choice = value end)
end
Button("Keep it and stop", 16, 116, Color3.fromRGB(46, 125, 80), "stop")
Button("Store it and keep spinning", 142, 116, Color3.fromRGB(45, 95, 170), "store")
Button("Discard and roll again", 268, 116, Color3.fromRGB(150, 60, 60), "roll")
while choice == nil and Alive() and Cfg.AutoSpin do task.wait(0.1) end
gui:Destroy()
return choice
end
local function StopSpin(message)
Cfg.AutoSpin = false
Save()
pcall(function() spinToggle.Value = false end)
Status(message)
SH.dbg("[SmoothHub] Auto Spin:", message)
pcall(function()
game:GetService("StarterGui"):SetCore("SendNotification", {
Title = "Smooth Hub - Auto Spin", Text = message, Duration = 10,
})
end)
end
task.spawn(function()
local fails = 0
local pendingLog, startLogged = false, false
local emptySlotOk, storesSinceRoll = false, 0
while Alive() do
task.wait(0.4)
if not Cfg.AutoSpin then
fails, pendingLog, startLogged, emptySlotOk, storesSinceRoll = 0, false, false, false, 0
Status("Off")
else
local roll = FindRoll()
if not roll then
OpenFamilyPage()
else
navTries = 0
local rolls = RollCount(roll)
local name, rarity = ReadFamily()
if name then
if not startLogged then
startLogged = true
LogResult(name, rarity, true)
elseif pendingLog then
pendingLog = false
LogResult(name, rarity, false)
end
end
local canRoll = false
if not name then
if emptySlotOk then
canRoll = true
else
Status("Can't read your current family yet...")
end
else
emptySlotOk = false
local label = ("%s (%s)"):format(Pretty(name), Cap(rarity))
local action = Decide(name, rarity)
if action == "ask" then
Status("Waiting for your choice: " .. label)
Notify("You rolled " .. label .. " - keep it? Answer in the box on screen.")
local choice = AskUser(name, rarity)
action = choice or "wait"
if choice == nil and Cfg.AutoSpin then
StopSpin("Stopped: couldn't show the question box, so you keep " .. label)
action = "wait"
end
end
if action == "stop" then
StopSpin("Stopped: you have " .. label)
elseif action == "store" then
if storesSinceRoll >= 2 then
StopSpin("Stopped: Store doesn't clear your family slot, so you keep " .. label)
else
local ok, why = StoreCurrent(name, rarity)
if ok then
storesSinceRoll += 1
emptySlotOk = true
SH.dbg("[SmoothHub] Auto Spin: stored " .. label)
Notify("Stored " .. label)
elseif why ~= "stopped" then
StopSpin(("Stopped: couldn't store %s (%s)"):format(label, why))
end
end
elseif action == "roll" then
canRoll = true
end
end
if canRoll then
if rolls ~= nil and rolls <= 0 then
StopSpin("Stopped: no rolls left")
else
Status(("Rolling... (%s left)"):format(rolls ~= nil and tostring(rolls) or "?"))
local before = rolls
local idx, used = PressRoll(roll, fails)
local registered = true
if idx == nil then
registered, used = false, "no usable press method"
elseif before ~= nil then
registered = WaitCountDrop(roll, before, 4, name, rarity)
end
if not registered then
fails += 1
SH.dbg(("[SmoothHub] Auto Spin: roll didn't register (%d/%d) via %s"):format(fails, #PRESS_METHODS, tostring(used)))
Status(("Roll didn't register (%d/%d) via %s"):format(fails, #PRESS_METHODS, tostring(used)))
if fails >= #PRESS_METHODS then StopSpin("Stopped: ROLL button doesn't respond (tried every press method)") end
else
if idx then pressPref = idx end
fails = 0
pendingLog = true
emptySlotOk, storesSinceRoll = false, 0
if Cfg.SpinUseDelay ~= false then
for _ = 1, math.floor(Cfg.SpinDelay * 10) do
if not Alive() or not Cfg.AutoSpin then break end
task.wait(0.1)
end
else
WaitForNewFamily(name, rarity, 2.5)
end
end
end
end
end
end
end
end)
end
local ReplicatedStorageSvc = game:GetService("ReplicatedStorage")
local StatusCategory = Cascade.Components.Section(Window, {
Title = "Status",
Disclosure = false
})
local AccountTab = StatusCategory:Tab({
Title = "Account",
Icon = Icon("bookmarkFill", "bookmark"),
Selected = true
})
local OverviewTab = AccountTab
local OverviewSection = OverviewTab:PageSection({
Title = "📊 Account Overview",
Subtitle = "Read from your player data. N/A = this value was not found."
})
local OverviewForm = OverviewSection:Form()
local function FindGui(path)
local cur = PlayerGui
for seg in path:gmatch("[^%.]+") do
cur = cur and cur:FindFirstChild(seg)
if not cur then return nil end
end
return cur
end
local function CountShards()
local items = FindGui("Interface.Inventory.Main.Holder.Items")
if not items then return nil end
local total = 0
for _, c in ipairs(items:GetChildren()) do
if c.Name:lower():find("shard", 1, true) then
local q = 1
local label = c:FindFirstChild("Quantity", true)
if label and (label:IsA("TextLabel") or label:IsA("TextButton")) then
q = tonumber((label.Text:gsub("[^%d]", ""))) or 1
end
total += q
end
end
return total
end
local function CurrentFamily()
local items = FindGui("Interface.Inventory.Main.Holder.Items")
if not items then return nil end
for _, c in ipairs(items:GetChildren()) do
local id, name = c.Name:match("^(%d+)%s*%-%s*(.+)$")
if id and tonumber(id) % 100 == 2 then
local main = c:FindFirstChild("Main")
local eq = main and main:FindFirstChild("Equipped")
if eq and eq:IsA("GuiObject") and eq.Visible then
local check = eq:FindFirstChild("Checkmark")
local on = true
if check and check:IsA("ImageLabel") and check.ImageTransparency >= 1 then on = false end
if on then return name end
end
end
end
return "None"
end
local function AttrTrue(v)
if type(v) == "boolean" then return v end
if type(v) == "number" then return v ~= 0 end
if type(v) == "string" then
local x = v:lower()
return x == "true" or x == "yes" or x == "1"
end
return false
end
local hiddenLabelCache, hiddenLabelAt = nil, 0
local function FindWarningLabel()
if os.clock() - hiddenLabelAt < 15 then return hiddenLabelCache end
hiddenLabelAt = os.clock()
hiddenLabelCache = false
local n = 0
for _, d in ipairs(PlayerGui:GetDescendants()) do
n += 1
if n % 600 == 0 then task.wait() end
if d:IsA("TextLabel") or d:IsA("TextButton") then
local t = (d.Text or ""):lower()
if t:find("exploiter", 1, true) or t:find("shadow ban", 1, true) or t:find("shadowban", 1, true) then
hiddenLabelCache = d:GetFullName():gsub("^Players%.[^%.]+%.PlayerGui%.", "")
break
end
end
end
return hiddenLabelCache
end
local function ShadowStatus()
if FindWarningLabel() then return "✅" end
if AttrTrue(LocalPlayer:GetAttribute("Exploiter")) or AttrTrue(LocalPlayer:GetAttribute("Blacklisted")) then
return "✅"
end
return "❌"
end
local function FlagCompare()
local others, flagged = 0, 0
for _, plr in ipairs(Players:GetPlayers()) do
if plr ~= LocalPlayer then
others += 1
if AttrTrue(plr:GetAttribute("Exploiter")) or AttrTrue(plr:GetAttribute("Blacklisted")) then
flagged += 1
end
end
end
local mine = (AttrTrue(LocalPlayer:GetAttribute("Exploiter")) or AttrTrue(LocalPlayer:GetAttribute("Blacklisted"))) and "flagged" or "clear"
return ("You: %s | Others flagged: %d/%d"):format(mine, flagged, others)
end
local FIELDS = {
{ key = "ShadowBan", title = "🛠️ Shadow Ban", raw = true, aliases = {}, calc = ShadowStatus,
colors = { ["✅"] = Color3.fromRGB(80, 200, 120), ["❌"] = Color3.fromRGB(235, 87, 87) } },
{ key = "FlagCompare", title = "🔍 Flag Compare", raw = true, aliases = {}, calc = FlagCompare },
{ key = "Blacklisted", title = "🚫 Blacklisted", flag = true, aliases = { "blacklisted" } },
{ key = "Exploiter", title = "⚠️ Exploiter Flag", flag = true, aliases = { "exploiter" } },
{ key = "Level", title = "▶️ Level", aliases = { "level", "lvl", "playerlevel" } },
{ key = "XP", title = "📈 XP", aliases = {}, strip = "^XP:%s*",
gui = "Interface.Gear_Up.XP.Percentage" },
{ key = "Prestige", title = "🔺 Prestige", aliases = { "prestige", "prestigelevel" } },
{ key = "Gold", title = "💰 Gold", aliases = { "gold", "coins", "money" },
gui = "Interface.Topbar.Main.Currencies.Gold.Amount" },
{ key = "Gems", title = "💎 Gems", aliases = { "gems", "gem" },
gui = "Interface.Topbar.Main.Currencies.Gems.Amount" },
{ key = "Family", title = "⚡ Family", aliases = { "family", "familyname", "clan" }, calc = CurrentFamily },
{ key = "Title", title = "🏷️ Title", aliases = { "title" }, pretty = true },
{ key = "Shards", title = "✨ Shards", aliases = { "shards", "shard" }, calc = CountShards },
}
for _, f in ipairs(FIELDS) do
local row = OverviewForm:Row()
row:Left():TitleStack({ Title = f.title })
f.label = row:Right():Label({ Text = "..." })
f.last = nil
end
local function NormName(s)
return (tostring(s):lower():gsub("[^%w]", ""))
end
local function Fmt(v)
if type(v) == "number" then
if v ~= math.floor(v) then return string.format("%.2f", v) end
local s = tostring(math.floor(v))
local neg = s:sub(1, 1) == "-"
if neg then s = s:sub(2) end
s = s:reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "")
return (neg and "-" or "") .. s
end
return tostring(v)
end
local function Truthy(v)
if type(v) == "boolean" then return v end
if type(v) == "number" then return v ~= 0 end
if type(v) == "string" then
local s = v:lower()
return s == "true" or s == "yes" or s == "1"
end
return false
end
local dataRoots = { LocalPlayer }
local lastRootScan = 0
local function ScanRoots(force)
if not force and os.clock() - lastRootScan < 15 then return end
lastRootScan = os.clock()
local roots = { LocalPlayer }
local uid = tostring(LocalPlayer.UserId)
local n = 0
for _, d in ipairs(ReplicatedStorageSvc:GetDescendants()) do
n += 1
if n % 500 == 0 then task.wait() end
if d.Name == LocalPlayer.Name or d.Name == uid then
table.insert(roots, d)
end
end
dataRoots = roots
end
local SKIP_NAMES = { PlayerGui = true, PlayerScripts = true }
local function CollectEntries()
local entries = {}
local function keep(v)
local t = type(v)
return t == "number" or t == "string" or t == "boolean"
end
local function walk(inst, depth)
pcall(function()
for k, v in pairs(inst:GetAttributes()) do
if keep(v) then
entries[#entries + 1] = { name = k, value = v, path = inst:GetFullName() .. " @" .. k }
end
end
if inst:IsA("ValueBase") then
local ok, v = pcall(function() return inst.Value end)
if ok and keep(v) then
entries[#entries + 1] = { name = inst.Name, value = v, path = inst:GetFullName() }
end
end
end)
if depth >= 6 then return end
for _, c in ipairs(inst:GetChildren()) do
if not SKIP_NAMES[c.Name] then walk(c, depth + 1) end
end
end
for _, root in ipairs(dataRoots) do
if root and root.Parent then walk(root, 0) end
end
return entries
end
local function IsExcludedPath(path)
return path:find("%.Boosts") ~= nil or path:find("%.Gamepasses") ~= nil
end
local function LookupField(entries, field)
for _, alias in ipairs(field.aliases) do
for _, e in ipairs(entries) do
if NormName(e.name) == alias and not IsExcludedPath(e.path) then
return e.value
end
end
end
return nil
end
local function ReadGui(path)
local cur = FindGui(path)
if cur and (cur:IsA("TextLabel") or cur:IsA("TextButton")) then
local t = (cur.Text or ""):gsub("<[^>]->", "")
t = t:gsub("^%s+", ""):gsub("%s+$", "")
if t ~= "" then return t end
end
return nil
end
local function LabelObject(label)
local inst = label and label.__instance
if not inst then return nil end
if inst:IsA("TextLabel") then return inst end
return inst:FindFirstChildWhichIsA("TextLabel", true)
end
local lastEntries = {}
local function RefreshOverview()
ScanRoots(false)
local entries = CollectEntries()
lastEntries = entries
for _, f in ipairs(FIELDS) do
local v
if f.calc then
local okC, r = pcall(f.calc)
if okC then v = r end
end
if v == nil and f.gui then v = ReadGui(f.gui) end
if v == nil then v = LookupField(entries, f) end
local text
if v == nil then
text = "N/A"
elseif f.raw then
text = tostring(v)
elseif f.flag then
text = Truthy(v) and "⚠️ Yes" or "✅ No"
elseif f.strip then
text = (tostring(v):gsub(f.strip, ""))
elseif f.pretty then
text = (tostring(v):gsub("_", " "))
else
text = Fmt(v)
end
if text ~= f.last then
f.last = text
pcall(function() f.label.Text = text end)
if f.colors and f.colors[text] then
pcall(function()
local obj = LabelObject(f.label)
if obj then obj.TextColor3 = f.colors[text] end
end)
end
end
end
end
task.spawn(function()
while Alive() do
pcall(RefreshOverview)
for _ = 1, 12 do
if not Alive() then return end
task.wait(0.25)
end
end
end)
task.spawn(function()
while Alive() do
if IN_MENU and Cfg.AutoSelect and not selecting then
if SelectCharacter() then
for _ = 1, 16 do
if not Alive() then return end
task.wait(0.25)
end
end
end
task.wait(1)
end
end)
task.spawn(function()
local lastFire = nil
local slotVisible, slotCheckAt = false, 0
while Alive() do
if not IN_MENU then
lastFire = nil
SetCountdown("N/A")
elseif not Cfg.AutoJoin then
lastFire = nil
SetCountdown("Off")
else
local now = os.clock()
if Cfg.AutoSelect then
if now >= slotCheckAt then
slotVisible = SlotScreenVisible()
slotCheckAt = now + 1
end
else
slotVisible = false
end
if slotVisible then
lastFire = nil
SetCountdown("Waiting for character select")
elseif SH.Coord.Blocker(100) then
lastFire = nil
SetCountdown("Waiting: " .. tostring(SH.Coord.last))
else
local delay = ClampDelay(Cfg.JoinDelay)
if not lastFire then lastFire = now end
local remaining = lastFire + delay - now
if remaining <= 0 then
SetCountdown("Joining now...")
lastFire = os.clock()
JoinLobby()
else
SetCountdown(("Next join in %ds"):format(math.ceil(remaining)))
end
end
end
task.wait(0.25)
end
end)
local MainCategory = Cascade.Components.Section(Window, {
Title = "In-Game",
Disclosure = false
})
local FarmTab = MainCategory:Tab({
Title = "Auto Farm",
Icon = Icon("boltFill"),
})
local Farm = {}
SH.Farm = Farm
function Farm.OnRetryAmount(amount) end
function Farm.Fire(name, ...)
local fn = Farm[name]
if type(fn) == "function" then
local ok, err = pcall(fn, ...)
if not ok then SH.dbg("[SmoothHub] Farm." .. tostring(name) .. ":", err) end
end
end
do
if Cfg.AutoLeave and Cfg.AutoLeaveWhenRetryComplete then Cfg.AutoLeave = false end
local MainForm = FarmTab:PageSection({
Title = "⚔️ Auto Farm",
Subtitle = "The essentials - turn these on and go",
}):Form()
local ReloadForm = FarmTab:PageSection({
Title = "🔄 Reload & Refill",
Subtitle = "Reload blades and refill supplies automatically",
}):Form()
local OptionsForm = FarmTab:PageSection({
Title = "⚙️ After the Match",
Subtitle = "What to do when a mission ends",
}):Form()
local function AddFarmToggle(form, title, subtitle, key, handler)
local row = form:Row()
row:Left():TitleStack({ Title = title, Subtitle = subtitle })
Farm.Toggles = Farm.Toggles or {}
Farm.Toggles[key] = UiSync.Link(row:Right():Toggle({
Value = Cfg[key],
ValueChanged = function(self, value)
Cfg[key] = value and true or false
Save()
Farm.Fire(handler, Cfg[key])
end
}), Cfg, key)
end
local function AddFarmSlider(form, titleFor, subtitle, key, handler)
local range = FARM_RANGES[key]
local row = form:Row()
local stack = row:Left():TitleStack({ Title = titleFor(Cfg[key]), Subtitle = subtitle })
UiSync.Link(row:Right():Slider({
Minimum = range[1],
Maximum = range[2],
Value = Cfg[key],
ValueChanged = function(self, value)
local v = math.clamp(math.floor(value + 0.5), range[1], range[2])
Cfg[key] = v
pcall(function() stack.Title = titleFor(v) end)
Save()
Farm.Fire(handler, v)
end
}), Cfg, key, function(_, want)
pcall(function() stack.Title = titleFor(want) end)
end)
end
local function AddCounterRow(form, titleFor, refreshHandler)
local row = form:Row()
local stack = row:Left():TitleStack({ Title = titleFor(0) })
local run
local btn = row:Right():Button({
Label = "Refresh",
Callback = function() if run then run() end end
})
run = BindButton(btn, function() Farm.Fire(refreshHandler) end)
return function(n)
pcall(function() stack.Title = titleFor(n) end)
end
end
-- ===== Enable [ Auto Farm ] + ดรอปดาวเลือกโหมด (Smart / Blades / Thunder Spears) ได้ทีละอย่าง =====
local FARM_MODES = { "Smart", "Blades", "Thunder Spears" }
local farmDrop, farmToggle, farmSyncing = nil, nil, false
local function FarmEnabled() return Cfg.AutoFarmSmart or Cfg.AutoFarm or Cfg.AutoFarmThunder end
local function FarmModeIdx()
if Cfg.AutoFarmSmart then return 1 end
if Cfg.AutoFarm then return 2 end
if Cfg.AutoFarmThunder then return 3 end
return table.find(FARM_MODES, Cfg.FarmMode) or 1
end
local function RefreshFarmUi()
local idx = FarmModeIdx()
if FarmEnabled() then Cfg.FarmMode = FARM_MODES[idx] end
farmSyncing = true
if farmToggle then pcall(function() farmToggle.Value = FarmEnabled() and true or false end) end
if farmDrop then pcall(function() farmDrop.Value = idx farmDrop.Label = FARM_MODES[idx] end) end
farmSyncing = false
end
-- idx: 0 = ปิด, 1 = Smart, 2 = Blades, 3 = Thunder Spears
local function ApplyFarmMode(idx)
Cfg.AutoFarmSmart = (idx == 1)
if idx == 0 then
Cfg.AutoFarm, Cfg.AutoFarmThunder = false, false
elseif idx == 2 then
Cfg.AutoFarm, Cfg.AutoFarmThunder = true, false
elseif idx == 3 then
Cfg.AutoFarm, Cfg.AutoFarmThunder = false, true
end
if idx > 0 then Cfg.FarmMode = FARM_MODES[idx] end
Save()
if idx == 0 then
Farm.Fire("OnAutoFarm", false)
Farm.Fire("OnAutoFarmThunder", false)
elseif Cfg.AutoFarm then
Farm.Fire("OnAutoFarm", true)
elseif Cfg.AutoFarmThunder then
Farm.Fire("OnAutoFarmThunder", true)
end
Farm.Fire("OnAutoFarmSmart", Cfg.AutoFarmSmart)
RefreshFarmUi()
end
do
local trow = MainForm:Row()
trow:Left():TitleStack({
Title = "Enable [ Auto Farm ]",
Subtitle = "Turn Auto Farm on or off.",
})
farmToggle = trow:Right():Toggle({
Value = FarmEnabled() and true or false,
ValueChanged = function(self, value)
if farmSyncing then return end
local want = value and true or false
if want == (FarmEnabled() and true or false) then return end
ApplyFarmMode(want and (table.find(FARM_MODES, Cfg.FarmMode) or 1) or 0)
end
})
local row = MainForm:Row()
row:Left():TitleStack({
Title = "Select Equip",
Subtitle = "Blades, Thunder Spears, or Smart (switches automatically).",
})
farmDrop = row:Right():PullDownButton({
Label = FARM_MODES[FarmModeIdx()],
Options = FARM_MODES,
Value = FarmModeIdx(),
ValueChanged = function(self, index)
if farmSyncing then return end
if type(index) == "table" then index = index[1] end
if type(index) ~= "number" or not FARM_MODES[index] then return end
self.Label = FARM_MODES[index]
if FarmEnabled() then
if index ~= FarmModeIdx() then ApplyFarmMode(index) end
else
Cfg.FarmMode = FARM_MODES[index] -- ยังปิดอยู่: จำโหมดไว้ รอกด Enable
Save()
end
end
})
pcall(StyleDropdown, farmDrop)
-- แถว Status: บอกว่า Auto Farm กำลังทำอะไร + ไททันเหลือกี่ตัว + ฆ่าไปแล้วกี่ตัว
-- สี: เหลือง = กำลังทำงาน | เขียว = ฟาร์มเสร็จ (ขึ้นหน้า MISSION COMPLETED) | สีปกติ = ปิดอยู่/ไม่ได้อยู่ในแมตช์
-- (อ่านค่าจากตัวแปรที่ระบบฟาร์มมีอยู่แล้ว ไม่แตะลูปฟาร์ม)
local statusRow = MainForm:Row()
statusRow:Left():TitleStack({ Title = "Status" })
local statusLabel = statusRow:Right():Label({ Text = "Off" })
local STATUS_YELLOW = Color3.fromRGB(255, 214, 10)
local STATUS_GREEN = Color3.fromRGB(48, 209, 88)
local statusDefault = nil
local lastStatus, lastColorKey = nil, nil
local function SetStatusColor(key) -- key: "yellow" | "green" | "default"
if key == lastColorKey then return end
local ok = pcall(function()
local o = LabelObject(statusLabel)
if not o then error("label not ready") end
if not statusDefault then statusDefault = o.TextColor3 end
if key == "yellow" then o.TextColor3 = STATUS_YELLOW
elseif key == "green" then o.TextColor3 = STATUS_GREEN
else o.TextColor3 = statusDefault end
end)
if ok then lastColorKey = key end
end
-- นับไททัน: seen = ตัวที่เคยเห็นว่ายังมีชีวิต ถ้าตัวนั้นตาย/หายไป = ฆ่าเพิ่ม 1 (นับรวมที่คนอื่นฆ่าด้วย)
local seen, kills = {}, 0
local lastSpawn, wasOn = nil, false
local function IsLive(t)
local hum = t and t.Parent and t:FindFirstChildOfClass("Humanoid")
return hum ~= nil and hum.Health > 0
end
local function CountTitans()
local folder = workspace:FindFirstChild("Titans")
local left = 0
if folder then
for _, t in ipairs(folder:GetChildren()) do
if t:IsA("Model") and IsLive(t) then
left += 1
seen[t] = true
end
end
end
for t in pairs(seen) do
if not IsLive(t) then
seen[t] = nil
kills += 1
end
end
return left
end
task.spawn(function()
while Alive() do
task.wait(0.3)
local text, color = "Off", "default"
pcall(function()
local on = FarmEnabled() or Cfg.AutoClearWave
if on and not wasOn then
kills = 0
seen = {}
end
wasOn = on
if not on then return end
if not Farm.CanUseHere() then text = "Not in a match" return end
-- เกิดตัวละครใหม่ = แมตช์ใหม่ เริ่มนับใหม่
if Farm.CharSpawnAt ~= lastSpawn then
lastSpawn = Farm.CharSpawnAt
kills = 0
seen = {}
end
-- ฟาร์มเสร็จ: หน้า MISSION COMPLETED ขึ้นแล้ว (หยุดนับ กันไททันหายตอนจบแมตช์ถูกนับเป็นฆ่า)
if Farm.CompleteScreenShown and Farm.CompleteScreenShown() then
text = ("Completed | Killed %d"):format(kills)
color = "green"
return
end
local left = CountTitans()
local action
if Farm.LootBusy then action = "Looting crate"
elseif Farm.NpcBusy then action = Farm.NpcCurrent and ("NPC visit: " .. tostring(Farm.NpcCurrent)) or "NPC visit"
elseif Farm.Refilling then action = "Refilling"
elseif Farm.FailSafeActive then action = "Fail safe"
elseif Farm.BladesBusy and Farm.BladesBusy() then action = "Reloading"
elseif Cfg.WaitForBoss and Farm.BossBlock and Farm.BossBlock() then action = tostring(Farm.BossStatus or "Waiting for boss")
elseif Cfg.AutoClearWave then action = tostring(Farm.ClearWaveStatus or "Clearing")
else action = tostring((Farm.Blades and Farm.Blades.Status) or "Farming") end
text = ("%s | Left %d | Killed %d"):format(action, left, kills)
color = "yellow"
end)
if text ~= lastStatus then
lastStatus = text
pcall(function() statusLabel.Text = text end)
lastColorKey = nil -- เปลี่ยนข้อความแล้ว ตั้งสีใหม่ทุกครั้ง กันสีถูกรีเซ็ต
end
SetStatusColor(color)
end
end)
UiSync.Add(RefreshFarmUi)
end
-- ตัวแทนสวิตช์เดิม: โค้ดส่วนอื่นยังสั่ง Farm.Toggles.AutoFarm.Value = ... ได้เหมือนเดิม และปุ่ม/ดรอปดาวจะอัปเดตตาม
do
local function Proxy(key, handler)
Farm.Toggles = Farm.Toggles or {}
Farm.Toggles[key] = setmetatable({}, {
__index = function(_, k) if k == "Value" then return Cfg[key] end end,
__newindex = function(_, k, v)
if k ~= "Value" then return end
Cfg[key] = v and true or false
Save()
RefreshFarmUi()
if handler then Farm.Fire(handler, Cfg[key]) end
end,
})
end
Proxy("AutoFarm", "OnAutoFarm")
Proxy("AutoFarmThunder", "OnAutoFarmThunder")
Proxy("AutoFarmSmart", "OnAutoFarmSmart")
end
AddFarmToggle(MainForm, "Bypass Farm (No Shadow Ban)",
"Auto-jumps while farming so your gas gets used.",
"SafeFarm", "OnSafeFarm")
AddFarmToggle(ReloadForm, "Auto Reload", "Reloads your blades when they run out.", "AutoReload", "OnAutoReload")
-- ===== Enable [ Auto Refill ] + ดรอปดาวติ๊กได้ทั้ง Blade และ Thunder Spears พร้อมกัน =====
do
local REFILL_OPTS = { "Blade", "Thunder Spears" }
local PICK_KEYS = { "RefillPickBlade", "RefillPickThunder" }
local LIVE_KEYS = { "AutoRefillsBlade", "AutoRefillsThunder" }
local LIVE_HANDLERS = { "OnAutoRefillsBlade", "OnAutoRefillsThunder" }
-- เซฟรุ่นเก่าที่เปิด Refill ไว้ -> ย้ายมาเป็น Enable + ตัวเลือกที่เคยเปิด
if not Cfg.RefillEnabled and (Cfg.AutoRefillsBlade or Cfg.AutoRefillsThunder) then
Cfg.RefillEnabled = true
Cfg.RefillPickBlade = Cfg.AutoRefillsBlade and true or false
Cfg.RefillPickThunder = Cfg.AutoRefillsThunder and true or false
end
-- ค่าที่ระบบเติมของใช้จริง = เปิดใช้งาน และติ๊กตัวเลือกนั้นไว้
local function SyncLive()
for i, live in ipairs(LIVE_KEYS) do
local want = (Cfg.RefillEnabled and Cfg[PICK_KEYS[i]]) and true or false
if Cfg[live] ~= want then
Cfg[live] = want
Farm.Fire(LIVE_HANDLERS[i], want)
end
end
Save()
end
SyncLive()
local function Picked()
local list = {}
for i, k in ipairs(PICK_KEYS) do if Cfg[k] then list[#list + 1] = i end end
return list
end
local syncing = false
local refillToggle, popBtn, fbBtn = nil, nil, nil
local OPTS4 = { "None", "Blade", "Thunder Spears", "Both" }
local function Idx4()
if Cfg.RefillPickBlade and Cfg.RefillPickThunder then return 4 end
if Cfg.RefillPickBlade then return 2 end
if Cfg.RefillPickThunder then return 3 end
return 1
end
local function RefreshRefillUi()
syncing = true
if refillToggle then pcall(function() refillToggle.Value = Cfg.RefillEnabled and true or false end) end
if popBtn then pcall(function() popBtn.Value = Picked() end) end
if fbBtn then pcall(function() fbBtn.Value = Idx4() fbBtn.Label = OPTS4[Idx4()] end) end
syncing = false
end
local function ApplyPicks(value)
local indexes = type(value) == "table" and value or { value }
local valid = SettingsStore.ValidIndexes(indexes, #REFILL_OPTS)
for i, k in ipairs(PICK_KEYS) do Cfg[k] = table.find(valid, i) ~= nil end
SyncLive()
end
local trow = ReloadForm:Row()
trow:Left():TitleStack({
Title = "Enable [ Auto Refill ]",
Subtitle = "Turn Auto Refill on or off.",
})
refillToggle = trow:Right():Toggle({
Value = Cfg.RefillEnabled and true or false,
ValueChanged = function(self, value)
if syncing then return end
Cfg.RefillEnabled = value and true or false
SyncLive()
end
})
local row = ReloadForm:Row()
row:Left():TitleStack({
Title = "Select Equip",
Subtitle = "Choose what to refill: Blade, Thunder Spears, or both.",
})
local okPop, btn = pcall(function()
return row:Right():PopUpButton({
Options = REFILL_OPTS,
Maximum = #REFILL_OPTS,
Value = Picked(),
ValueChanged = function(self, value)
if syncing then return end
ApplyPicks(value)
end
})
end)
if okPop and btn then
popBtn = btn
pcall(StyleDropdown, btn)
else
-- สำรอง: ถ้า PopUpButton ใช้ไม่ได้ ใช้ดรอปดาวเลือกทีละแบบ (รวมตัวเลือก Both)
local map = { {}, { 1 }, { 2 }, { 1, 2 } }
fbBtn = row:Right():PullDownButton({
Label = OPTS4[Idx4()],
Options = OPTS4,
Value = Idx4(),
ValueChanged = function(self, index)
if syncing then return end
if type(index) == "table" then index = index[1] end
if type(index) ~= "number" or not map[index] then return end
self.Label = OPTS4[index]
ApplyPicks(map[index])
end
})
pcall(StyleDropdown, fbBtn)
end
UiSync.Add(function()
-- โหลดคอนฟิกใหม่: อัปเดตปุ่ม/ดรอปดาว และคำนวณค่าที่ใช้จริงใหม่
SyncLive()
RefreshRefillUi()
end)
end
-- แถว Status ของหมวด Reload & Refill: โชว์ว่ากำลังรีโหลด/เติมของอยู่ไหม (อ่านจากตัวแปรที่ระบบมีอยู่แล้ว)
do
local statusRow = ReloadForm:Row()
statusRow:Left():TitleStack({ Title = "Status" })
local statusLabel = statusRow:Right():Label({ Text = "Off" })
local lastStatus = nil
task.spawn(function()
while Alive() do
task.wait(0.5)
local text
pcall(function()
if not (Cfg.AutoReload or Cfg.AutoRefillsBlade or Cfg.AutoRefillsThunder) then text = "Off"
elseif not Farm.CanUseHere() then text = "Not in a match"
elseif Farm.Refilling then text = tostring((Farm.RefillStatus and Farm.RefillStatus()) or "Refilling")
elseif Cfg.AutoReload and Farm.BladesBusy and Farm.BladesBusy() then text = "Reloading"
else
local st = Farm.RefillStatus and Farm.RefillStatus()
text = (st and st ~= "Idle") and tostring(st) or "Ready"
end
end)
text = text or "Ready"
if text ~= lastStatus then
lastStatus = text
pcall(function() statusLabel.Text = text end)
end
end
end)
end
AddFarmToggle(MainForm, "Damage Multiplier ( x10 )", "Deal 10x damage to Titans.", "DamageMultiplier", "OnDamageMultiplier")
AddFarmSlider(MainForm, function(v) return "Set Start Farm ( " .. v .. "s )" end,
"Wait after spawning before farming. 0 = start right away.", "StartDelay", "OnStartDelay")
AddFarmSlider(MainForm, function(v) return "Fly Height ( " .. v .. " studs )" end,
"How high you fly above the titan (150-500). Higher is safer.", "FlyHeight", "OnFlyHeight")
-- [hidden] AddFarmSlider(MainForm, function(v) return "Thunder Spears Range ( " .. v .. " studs )" end,
-- [hidden] "Auto Farm [ TS ]: hits every titan within this distance of you (50-10000).", "ThunderRange", nil)
-- [hidden] AddFarmSlider(MainForm, function(v) return "Thunder Spears Targets per Shot ( " .. ((v <= 0 or v >= 200) and "All" or v) .. " )" end,
-- [hidden] "Auto Farm [ TS ]: titans hit per shot. 0 or 200 = all of them in range.", "ThunderTargets", nil)
AddFarmToggle(OptionsForm, "Auto Retry", "Automatically Retry when completed.", "AutoRetry", "OnAutoRetry")
AddFarmToggle(OptionsForm, "Auto Leave", "Leave to the lobby right when the match ends.", "AutoLeave", "OnAutoLeave")
AddFarmToggle(OptionsForm, "Auto Leave After X Matches",
"Retry up to the Retry Amount, then leave (needs Auto Retry). Not together with Auto Leave.",
"AutoLeaveWhenRetryComplete", "OnAutoLeaveWhenRetryComplete")
AddFarmSlider(OptionsForm, function(v) return "Retry Amount ( " .. v .. " )" end,
"Matches to retry before leaving (used by Auto Leave After X Matches).", "RetryAmount", "OnRetryAmount")
Farm.SetRetry = AddCounterRow(OptionsForm, function(n) return "Current Retry ( " .. tostring(n) .. " )" end, "RefreshRetry")
do
local RtRow = OptionsForm:Row()
RtRow:Left():TitleStack({ Title = "Status" })
local RtLabel = RtRow:Right():Label({ Text = "Off" })
local lastRt = nil
function Farm.SetRetryStatus(text)
if text == lastRt then return end
lastRt = text
pcall(function() RtLabel.Text = tostring(text) end)
end
end
local SafetyForm = FarmTab:PageSection({
Title = "🛟 Safety",
Subtitle = "Keep missions from finishing too fast and break long win streaks",
}):Form()
AddFarmSlider(SafetyForm, function(v) return "Safe Time ( " .. v .. "s )" end,
"A mission can't finish sooner than this. 0 = off.", "SafeTime", "OnSafeTime")
AddFarmSlider(SafetyForm, function(v) return "Fail Safe At [ " .. v .. " ]" end,
"Lose on purpose after this many wins in a row. 0 = never.", "FailSafeAt", "OnFailSafeAt")
Farm.SetStreak = AddCounterRow(SafetyForm, function(n) return "Current Streak ( " .. tostring(n) .. " )" end, "RefreshStreak")
function Farm.RefreshStreak()
if Farm.SetStreak then Farm.SetStreak(Cfg.Streak) end
end
task.defer(Farm.RefreshStreak)
AddFarmToggle(SafetyForm, "Auto Fail Safe", "When the win streak reaches the number above, the next match is lost on purpose: you stand still and let the titans kill you, then the streak resets.", "AutoFailSafe", "OnAutoFailSafe")
do
local FsRow = SafetyForm:Row()
FsRow:Left():TitleStack({ Title = "Fail Safe Status" })
local FsLabel = FsRow:Right():Label({ Text = "Off" })
local lastFs = nil
function Farm.SetFailSafeStatus(text)
if text == lastFs then return end
lastFs = text
pcall(function() FsLabel.Text = tostring(text) end)
end
end
end
Farm.Debug = false
function Farm.CanUseHere()
return not IN_MENU and game.PlaceId ~= 14916516914
end
do
local RunService = game:GetService("RunService")
local ATTACK_GAP = { [0] = 0.4, [1] = 0.8, [2] = 0.3, [3] = 0.2, [4] = 0.12, [5] = 0.08 }
local OTHER_PARTS = { "Eyes", "LeftArm", "RightArm", "LeftLeg", "RightLeg" }
local ARRIVE_DIST = 12
local ATTACK_BEFORE_ARRIVE = true
local ORBIT_ON = true
local ORBIT_RADIUS = 14
local ORBIT_SPEED = 1.6
local ORBIT_BOB = 5
local HIT_COUNT = 10 -- จำนวนไททันที่ฟันหลังคอพร้อมกันต่อรอบ (ตัวหลัก + ตัวใกล้สุดอีก HIT_COUNT-1) | เดิม 9
local DAMAGE_MULT = 10
local BOSS_MULT = 10 -- ตัวคูณดาเมจใส่ Colossal Titan (ตอนมีบอส) | ไททันตัวเล็กยังใช้ BOSS_SMALL_MULT (x10) | ปรับเลขนี้ได้
local OTHER_MULT = 10
local RAID_SMALL = HIT_COUNT - 1 -- ตอนมี Colossal: ฟันไททันตัวเล็กได้เต็มโควตาเหมือนตอนไม่มีบอส (เดิม 3 ตัว)
local BOSS_ALWAYS = true
local PRIORITY = {}
local BOSS_NORMAL_HP = 0
local BOSS_SMALL_MULT = DAMAGE_MULT -- ตอนมี Colossal ไททันตัวเล็กก็โดน x10 เหมือนบอส (เดิม x1)
local MultState = { full = DAMAGE_MULT, other = OTHER_MULT }
local function Mult() return Cfg.DamageMultiplier and MultState.full or 1 end
local function OtherMult() return Cfg.DamageMultiplier and math.min(MultState.other, MultState.full) or 1 end
local B = { Target = nil, Hits = 0, Status = "Idle" }
Farm.Blades = B
local post = nil
local function Post()
if not post or not post.Parent then
local ok, r = pcall(function()
return game:GetService("ReplicatedStorage").Assets.Remotes:WaitForChild("POST", 5)
end)
post = ok and r or nil
end
return post
end
local function MyRoot()
local char = LocalPlayer.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
local root = char and char:FindFirstChild("HumanoidRootPart")
if char and hum and root and hum.Health > 0 then return root, char end
return nil
end
local function HitParts(titan)
local hb = titan:FindFirstChild("Hitboxes")
return hb and hb:FindFirstChild("Hit")
end
local function NapeOf(titan)
local hit = HitParts(titan)
local nape = hit and hit:FindFirstChild("Nape")
if nape and nape:IsA("BasePart") then return nape end
return nil
end
local function TitanAlive(titan)
local hum = titan and titan.Parent and titan:FindFirstChildOfClass("Humanoid")
return hum ~= nil and hum.Health > 0
end
-- 🛰️ Boss Check (Raid): เช็กว่าบอส "เกิดครบและพร้อมสู้" แล้วหรือยัง ก่อนเริ่มฟาร์ม
-- บอสบางครั้งโผล่ในโฟลเดอร์ Titans แล้วแต่ยังโหลดไม่ครบ / ยืนนิ่ง ไม่ขยับ (บั๊กของเกม)
-- ลำดับ: ยังไม่มีบอส -> โหลดไม่ครบ (ไม่มี Humanoid/Root/Nape หรือ Root ยังถูกล็อก) -> รอให้นิ่ง BossSettle วินาที -> พร้อม
-- "active" = เห็นบอสขยับ / เปลี่ยนท่าอนิเมชัน / เลือดลด (ถ้าไม่ active จะแสดงว่า idle แต่ยังฟาร์มได้ เพราะบางทีบอสตื่นตอนโดนตี)
local BOSS_PATTERNS = { "^colossal", "^armored", "^female", "^attack" }
local BOSS_GIVEUP = 120 -- ถ้ารอบอสที่โหลดไม่ครบเกินนี้ (วินาที) จะเลิกรอแล้วฟาร์มต่อ กันค้างถาวร
local bossInfo = setmetatable({}, { __mode = "k" })
Farm.BossStatus = "Off"
local function IsBossName(n)
n = tostring(n):lower()
if not n:find("titan", 1, true) then return false end
for _, pat in ipairs(BOSS_PATTERNS) do
if n:find(pat) then return true end
end
return false
end
function Farm.FindBoss()
local folder = workspace:FindFirstChild("Titans")
for _, t in ipairs(folder and folder:GetChildren() or {}) do
if t:IsA("Model") and IsBossName(t.Name) then return t end
end
return nil
end
local function PlayingAnimIds(boss)
local ids, n = {}, 0
local ok, animator = pcall(function() return boss:FindFirstChildWhichIsA("Animator", true) end)
if ok and animator then
local ok2, tracks = pcall(function() return animator:GetPlayingAnimationTracks() end)
if ok2 and tracks then
for _, tr in ipairs(tracks) do
local id = tr.Animation and tr.Animation.AnimationId
if id and id ~= "" then ids[id] = true n += 1 end
end
end
end
return ids, n
end
-- คืน state: "loading" | "settling" | "ready" | "dead", พร้อมเหตุผล/รายละเอียด
function Farm.BossReadiness(boss)
local now = os.clock()
local hum = boss:FindFirstChildOfClass("Humanoid")
local root = boss.PrimaryPart or boss:FindFirstChild("HumanoidRootPart")
if not hum then return "loading", "no Humanoid" end
if hum.Health <= 0 then return "dead", "defeated" end
if not root then return "loading", "no root part" end
if not NapeOf(boss) then return "loading", "no nape hitbox" end
if hum.MaxHealth <= 0 then return "loading", "no health yet" end
if root.Anchored then return "loading", "root still anchored" end
local info = bossInfo[boss]
if not info then
local ids = PlayingAnimIds(boss)
info = { seen = now, pos = root.Position, hp = hum.Health, ids = ids, active = false }
bossInfo[boss] = info
end
if not info.active then
if (root.Position - info.pos).Magnitude > 2 or hum.Health < info.hp - 0.5 then
info.active = true
else
local ids = PlayingAnimIds(boss)
for id in pairs(ids) do
if not info.ids[id] then info.active = true break end
end
end
end
local age = now - info.seen
local settle = Cfg.BossSettle or 5
if age < settle then return "settling", math.ceil(settle - age) end
return "ready", info.active and "active" or "idle", age
end
local bossCacheAt, bossCacheVal, bossFirstSeen = 0, false, nil
-- true = ต้องรอ (อย่าเพิ่งฟาร์ม)
function Farm.BossBlock()
if not Cfg.WaitForBoss then Farm.BossStatus = "Off" return false end
local now = os.clock()
if now - bossCacheAt < 0.25 then return bossCacheVal end
bossCacheAt = now
local settle = Cfg.BossSettle or 5
local boss = Farm.FindBoss()
local block = false
if not boss then
bossFirstSeen = nil
local since = now - (Farm.CharSpawnAt or now)
if since < settle then
Farm.BossStatus = ("Waiting for boss to spawn (%ds)"):format(math.ceil(settle - since))
block = true
else
Farm.BossStatus = "No boss in this match"
end
else
bossFirstSeen = bossFirstSeen or now
local st, a = Farm.BossReadiness(boss)
if st == "loading" then
if now - bossFirstSeen > BOSS_GIVEUP then
Farm.BossStatus = "Boss not loading (" .. tostring(a) .. ") - farming anyway"
else
Farm.BossStatus = "Boss loading: " .. tostring(a)
block = true
end
elseif st == "settling" then
Farm.BossStatus = ("Boss spawned, settling (%ds)"):format(tonumber(a) or 0)
block = true
elseif st == "dead" then
Farm.BossStatus = "Boss defeated"
else
Farm.BossStatus = (a == "active") and "Boss ready (active)" or "Boss ready (idle - not moving yet)"
end
end
bossCacheVal = block
return block
end
function Farm.BossDump()
local folder = workspace:FindFirstChild("Titans")
local names = {}
for _, t in ipairs(folder and folder:GetChildren() or {}) do names[#names + 1] = t.Name end
print("[SmoothHub][Boss] Titans in folder (" .. #names .. "): " .. table.concat(names, ", "))
local boss = Farm.FindBoss()
if not boss then print("[SmoothHub][Boss] no boss model matched " .. table.concat(BOSS_PATTERNS, " ")) return end
local hum = boss:FindFirstChildOfClass("Humanoid")
local root = boss.PrimaryPart or boss:FindFirstChild("HumanoidRootPart")
local ids, n = PlayingAnimIds(boss)
local idList = {}
for id in pairs(ids) do idList[#idList + 1] = id end
local st, a = Farm.BossReadiness(boss)
print(("[SmoothHub][Boss] %s | state=%s (%s) | health=%s/%s | anchored=%s | nape=%s | tracks=%d"):format(
boss:GetFullName(), tostring(st), tostring(a),
hum and tostring(math.floor(hum.Health)) or "?", hum and tostring(math.floor(hum.MaxHealth)) or "?",
root and tostring(root.Anchored) or "?", tostring(NapeOf(boss) ~= nil), n))
if #idList > 0 then print("[SmoothHub][Boss] playing animations: " .. table.concat(idList, ", ")) end
for k, v in pairs(boss:GetAttributes()) do print(("[SmoothHub][Boss] attribute %s = %s"):format(tostring(k), tostring(v))) end
end
Farm.DefendErenActive = false
function Farm.ErenRoot()
local c = Farm.ErenCache
if c and c.Parent then return c end
Farm.ErenCache = nil
Farm.ErenModel = nil
if os.clock() < (Farm.ErenSearchAt or 0) then return nil end
Farm.ErenMisses = Farm.ErenMisses or 0
Farm.ErenSearchAt = os.clock() + math.min(3 + Farm.ErenMisses * 3, 20)
local chars = workspace:FindFirstChild("Characters")
local titans = workspace:FindFirstChild("Titans")
local exact, loose = nil, nil
for _, d in ipairs(workspace:GetDescendants()) do
if d:IsA("Model") and not (chars and d:IsDescendantOf(chars)) and not (titans and d:IsDescendantOf(titans)) then
local n = d.Name:lower()
if n:find("eren", 1, true) then
if n == "eren" or n == "eren_yeager" or n == "eren yeager" or n == "erenyeager" then
exact = exact or d
else
loose = loose or d
end
end
end
end
local m = exact or loose
if not m then Farm.ErenMisses += 1 return nil end
Farm.ErenMisses = 0
local part = m.PrimaryPart or m:FindFirstChild("HumanoidRootPart") or m:FindFirstChildWhichIsA("BasePart", true)
if not part then return nil end
Farm.ErenCache = part
Farm.ErenModel = m
return part
end
function Farm.ErenThreats()
local eren = Farm.ErenRoot()
if not eren then return 0, nil end
local folder = workspace:FindFirstChild("Titans")
local radius = Cfg.DefendErenRadius or 400
local n = 0
for _, t in ipairs(folder and folder:GetChildren() or {}) do
if t:IsA("Model") and t.Name ~= "Colossal_Titan" and TitanAlive(t) then
local nape = NapeOf(t)
if nape and (nape.Position - eren.Position).Magnitude <= radius then n = n + 1 end
end
end
return n, eren
end
local function PickTitan(root)
local folder = workspace:FindFirstChild("Titans")
if not folder then return nil end
local erenPart = Farm.DefendErenActive and Farm.ErenRoot() or nil
local radius = Cfg.DefendErenRadius or 400
local best, bestDist = nil, math.huge
for _, t in ipairs(folder:GetChildren()) do
if t:IsA("Model") and TitanAlive(t) and not (erenPart and t.Name == "Colossal_Titan") then
local nape = NapeOf(t)
if nape then
local d
if erenPart then
d = (nape.Position - erenPart.Position).Magnitude
if d > radius then d = nil end
else
d = (nape.Position - root.Position).Magnitude
end
if d and d < bestDist then best, bestDist = t, d end
end
end
end
return best
end
local function Running()
if not (Cfg.AutoFarm or Cfg.AutoFarmThunder) or not Farm.CanUseHere() then return false end
if Farm.Refilling then return false end
if Farm.FailSafeActive then return false end
if Farm.PauseUntil and Farm.PauseUntil > os.clock() then return false end
return true
end
-- 🚦 จำกัดจำนวนคำสั่ง Hitboxes.Register ต่อวินาที กัน remote ท่วมจนปิงพุ่ง (Damage Multiplier x10 + Attack Speed สูง)
-- เกินงบแล้วจะลดจำนวนรอบคูณลง (ต่ำสุด 1 รอบ = ฟาร์มยังทำงานปกติ) | ปรับ REMOTE_BUDGET ได้, 0 = ไม่จำกัด
local REMOTE_BUDGET = 150
local budgetStart, budgetUsed = os.clock(), 0
local function Reps(nParts)
local want = Mult()
if REMOTE_BUDGET <= 0 or want <= 1 then return want end
local now = os.clock()
if now - budgetStart >= 1 then budgetStart, budgetUsed = now, 0 end
local n = math.max(nParts, 1)
local budget = REMOTE_BUDGET * math.max(1, want / DAMAGE_MULT)
local reps = math.max(1, math.min(want, math.floor((budget - budgetUsed) / n)))
budgetUsed = budgetUsed + reps * n
return reps
end
-- 📦 ตัวส่ง remote เป็นชุด: เข้าคิวก่อน แล้วปล่อยทีละ NET_BATCH คำสั่งต่อเฟรม (เรียงลำดับเดิมทุกคำสั่ง)
-- แทนที่จะยิงร้อยกว่าคำสั่งในเฟรมเดียว | คิวยาวเกิน NET_QUEUE_MAX = ข้ามรอบโจมตีนั้น กันคิวค้างสะสม
local NET_BATCH = 12
local NET_QUEUE_MAX = 400
local netQ, netHead, netTail = {}, 1, 0
local function NetPending() return netTail - netHead + 1 end
local function NetPush(remote, ...)
netTail = netTail + 1
netQ[netTail] = { remote = remote, args = table.pack(...) }
end
local netConn
netConn = RunService.Heartbeat:Connect(function()
if not Alive() then
if netConn then netConn:Disconnect() end
return
end
local sent = 0
while netHead <= netTail and sent < NET_BATCH do
local it = netQ[netHead]
netQ[netHead] = nil
netHead = netHead + 1
sent = sent + 1
if it.remote and it.remote.Parent then
pcall(it.remote.FireServer, it.remote, table.unpack(it.args, 1, it.args.n))
end
end
if netHead > netTail then netHead, netTail = 1, 0 end
end)
local function Slash(parts)
local remote = Post()
if not remote then return false end
if typeof(parts) == "Instance" then parts = { parts } end
if NetPending() > NET_QUEUE_MAX then return false end
local reps = Reps(#parts)
NetPush(remote, "Attacks", "Slash", true)
for r = 1, reps do
for i, part in ipairs(parts) do
if PRIORITY[part] or r <= OtherMult() then
NetPush(remote, "Hitboxes", "Register", part, 143 + math.random() * 10, 0.26 + math.random() * 0.25)
end
end
end
NetPush(remote, "Attacks", "Slash", false)
B.Hits = B.Hits + #parts
return true
end
-- 🎯 Thunder Spears: จำนวนเป้าต่อชุด (0 หรือ 200 = ทุกตัว) | ใช้ค่าจากสไลเดอร์ แทน HIT_COUNT ของดาบ
local function SpearLimit()
local n = Cfg.ThunderTargets or 0
if n <= 0 or n >= 200 then return math.huge end
return n
end
local function NapesAround(main, spear)
local mainNape = NapeOf(main)
if not mainNape then return {} end
local cap = spear and SpearLimit() or HIT_COUNT
local rangeFrom, rangeMax = nil, Cfg.ThunderRange or 10000
if spear then
local rr = MyRoot()
rangeFrom = rr and rr.Position or mainNape.Position
end
local folder = workspace:FindFirstChild("Titans")
local boss, bossNape = nil, nil
if BOSS_ALWAYS and folder then
local c = folder:FindFirstChild("Colossal_Titan")
if c and c:IsA("Model") and TitanAlive(c) then
bossNape = NapeOf(c)
if bossNape then boss = c end
end
end
local others = {}
for _, t in ipairs(folder and folder:GetChildren() or {}) do
if t ~= main and t ~= boss and t:IsA("Model") and TitanAlive(t) then
local n = NapeOf(t)
if n and (not spear or (n.Position - rangeFrom).Magnitude <= rangeMax) then
others[#others + 1] = { part = n, d = (n.Position - mainNape.Position).Magnitude }
end
end
end
table.sort(others, function(a, b) return a.d < b.d end)
local list = {}
if boss then
PRIORITY = { [bossNape] = true }
list[1] = bossNape
local hum = boss:FindFirstChildOfClass("Humanoid")
local ratio = hum and (hum.Health / math.max(hum.MaxHealth, 1)) or 1
if ratio <= BOSS_NORMAL_HP then
MultState.full, MultState.other = 1, 1
else
MultState.full, MultState.other = BOSS_MULT, BOSS_SMALL_MULT
end
local smallLeft = spear and (cap - 1) or math.min(RAID_SMALL, HIT_COUNT - 1)
if main ~= boss and smallLeft > 0 then
list[#list + 1] = mainNape
smallLeft = smallLeft - 1
end
for i = 1, math.min(smallLeft, #others) do
list[#list + 1] = others[i].part
end
else
MultState.full, MultState.other = DAMAGE_MULT, OTHER_MULT
PRIORITY = { [mainNape] = true }
list[1] = mainNape
for i = 1, math.min(cap - 1, #others) do
list[#list + 1] = others[i].part
end
end
return list
end
local function TurnOffOther(key)
if Cfg[key] then
Cfg[key] = false
Save()
local ctl = Farm.Toggles and Farm.Toggles[key]
if ctl then pcall(function() ctl.Value = false end) end
end
end
local function Stopped()
if Cfg.AutoFarm or Cfg.AutoFarmThunder then return end
B.Target = nil
B.Status = "Idle"
local root = MyRoot()
if root then pcall(function() root.AssemblyLinearVelocity = Vector3.zero end) end
end
function Farm.OnAutoFarm(on)
if on then TurnOffOther("AutoFarmThunder") TurnOffOther("AutoClearWave") end
Stopped()
end
function Farm.OnAutoFarmThunder(on)
if on then TurnOffOther("AutoFarm") TurnOffOther("AutoClearWave") end
Stopped()
end
-- Safe Farm เป็นตัวเสริมของ Auto Farm (ลูปฟาร์มต้องมี AutoFarm หรือ AutoFarmThunder เปิดอยู่)
function Farm.OnSafeFarm(on)
if on and not (Cfg.AutoFarm or Cfg.AutoFarmThunder or Cfg.AutoClearWave) then
Cfg.AutoFarm = true
Save()
local ctl = Farm.Toggles and Farm.Toggles.AutoFarm
if ctl then pcall(function() ctl.Value = true end) end
Farm.Fire("OnAutoFarm", true)
end
end
-- ============================================================
-- 🧠 Auto Farm [ Smart ]: ดูว่ากำลังใช้อาวุธอะไร แล้วสลับ Auto Farm ให้ตรงกัน
-- ตัวละครมี Attribute "Spears" (จำนวนหอกที่เหลือ) เมื่อใช้ Thunder Spears -> ฟาร์มหอก
-- ไม่มี Attribute นี้ -> ฟาร์มดาบ | ถ้าเปิด Auto Clear Wave อยู่จะไม่ยุ่ง
-- ============================================================
local function HeldWeapon()
local char = LocalPlayer.Character
if not char then return nil end
if type(char:GetAttribute("Spears")) == "number" then return "thunder" end
return "blade"
end
local function SetFarmMode(wantThunder)
local key, other = "AutoFarm", "AutoFarmThunder"
if wantThunder then key, other = other, key end
if Cfg[key] and not Cfg[other] then return end
Cfg[other] = false
Cfg[key] = true
Save()
local t = Farm.Toggles
if t and t[other] then pcall(function() t[other].Value = false end) end
if t and t[key] then pcall(function() t[key].Value = true end) end
Farm.Fire(wantThunder and "OnAutoFarmThunder" or "OnAutoFarm", true)
SH.say("Smart Auto Farm: " .. (wantThunder and "Thunder Spears" or "Blades"))
end
function Farm.OnAutoFarmSmart(on) end
task.spawn(function()
local lastSeen, same = nil, 0
while Alive() do
task.wait(1)
if Cfg.AutoFarmSmart and Farm.CanUseHere() and not Cfg.AutoClearWave and not SH.QuestHold then
local w = HeldWeapon()
if w == nil then
lastSeen, same = nil, 0
elseif w == lastSeen then
same += 1
else
lastSeen, same = w, 1
end
-- ต้องเห็นอาวุธเดิมติดกัน 2 รอบก่อนสลับ (กันสลับรัวตอนเกิดใหม่/โหลดตัวละคร)
if same >= 2 then SetFarmMode(w == "thunder") end
else
lastSeen, same = nil, 0
end
end
end)
local getRemote = nil
local function Get()
if not getRemote or not getRemote.Parent then
local ok, r = pcall(function()
return game:GetService("ReplicatedStorage").Assets.Remotes:WaitForChild("GET", 5)
end)
getRemote = ok and r or nil
end
return getRemote
end
local function SpearsLeft()
local char = LocalPlayer.Character
local n = char and char:GetAttribute("Spears")
if type(n) == "number" then return n end
return nil
end
Farm.SpearsLeft = SpearsLeft
local lastSide, lastCount = 8, nil
local usedIds = {}
local stuck, stuckCount = 0, nil
local stuckAt = 0
local function NextSpearId(count)
if lastCount ~= nil and count ~= nil and count > lastCount then usedIds = {} end
lastCount = count
if count == nil or count > 8 then
usedIds = {}
lastSide = (lastSide == 8) and 7 or 8
return lastSide
end
local char = LocalPlayer.Character
local rig = char and char:FindFirstChild("Rig_" .. LocalPlayer.Name)
local gameUsed = {}
for _, armName in ipairs({ "RightLowerArm", "LeftLowerArm" }) do
local arm = rig and rig:FindFirstChild(armName)
local folder = arm and arm:FindFirstChild("Spears")
for _, d in ipairs(folder and folder:GetChildren() or {}) do
if d.Name:match("^%d+$") and d:GetAttribute("Used") then gameUsed[tonumber(d.Name)] = true end
end
end
local free = {}
for id = 1, 8 do
if not usedIds[id] and not gameUsed[id] then free[#free + 1] = id end
end
if #free == 0 then return nil end
local pick = free[math.random(1, #free)]
usedIds[pick] = true
return pick
end
function Farm.SpearStuck()
return stuck >= 4 and SpearsLeft() == stuckCount
end
local fireBusy, fireBusyAt = false, 0
local function Spear(parts)
local get, remote = Get(), Post()
if not get or not remote then return false end
local left = SpearsLeft()
if left ~= nil and left <= 0 then return false end
if stuck > 0 and left ~= stuckCount then stuck = 0 end
if Farm.SpearStuck() then
-- [smooth] หอกยังเหลือ = ไม่ได้หมดจริง: พัก 1s แล้วลองยิงใหม่ ไม่หยุดยิงถาวร
if left ~= nil and left > 0 and os.clock() - stuckAt > 1 then
stuck, usedIds = 0, {}
else
return false
end
end
if fireBusy and os.clock() - fireBusyAt < 10 then return false end -- S_Fire ก่อนหน้ายังรอเซิร์ฟเวอร์อยู่
local id = NextSpearId(left)
if id == nil then
usedIds = {}
stuck, stuckCount, stuckAt = stuck + 1, left, os.clock()
return false
end
local done = false
fireBusy, fireBusyAt = true, os.clock()
task.spawn(function()
pcall(function() get:InvokeServer("Spears", "S_Fire", tostring(id)) end)
done = true
fireBusy = false
end)
local t0 = os.clock()
while not done and os.clock() - t0 < 1.5 do task.wait() end
-- [smooth] ไม่หน่วงตายตัว 0.35s: เช็กจำนวนหอกทุกเฟรม ออกทันทีที่ลด (เพดานรอ 1s เท่าเดิม)
local after = SpearsLeft()
if after == nil then task.wait(0.1) end
local tw = os.clock()
while left ~= nil and after ~= nil and after >= left and os.clock() - tw < 1.0 do
task.wait()
after = SpearsLeft()
end
if left ~= nil and after ~= nil and after >= left then
usedIds[id] = nil
stuck, stuckCount, stuckAt = stuck + 1, left, os.clock()
if stuck == 4 then print("[SmoothHub][Thunder] S_Fire not accepted 4 times in a row, spears left =", tostring(left), "id =", tostring(id)) end
return false
end
stuck = 0
local reps = Reps(#parts)
if NetPending() <= NET_QUEUE_MAX then
for r = 1, reps do
for i, part in ipairs(parts) do
if part and part.Parent and (PRIORITY[part] or r <= OtherMult()) then
NetPush(remote, "Spears", "S_Explode", part.Position, 0.10 + math.random() * 0.09)
end
end
end
end
for _, part in ipairs(parts) do
if part and part.Parent then B.Hits = B.Hits + 1 end
end
return true
end
local SAFE = { start = os.clock(), zeroSince = nil, n = 0, lastLog = 0 }
local function SafeLeft()
local need = Cfg.SafeTime or 0
if need <= 0 or not Farm.CanUseHere() then return 0 end
return math.max(0, need - (os.clock() - SAFE.start))
end
Farm.SafeLeft = SafeLeft
task.spawn(function()
while Alive() do
task.wait(0.5)
local now = os.clock()
if not Farm.CanUseHere() then
SAFE.start, SAFE.zeroSince, SAFE.n = now, nil, 0
else
local folder = workspace:FindFirstChild("Titans")
local n = 0
for _, t in ipairs(folder and folder:GetChildren() or {}) do
if t:IsA("Model") and TitanAlive(t) then n = n + 1 end
end
SAFE.n = n
if n == 0 then
SAFE.zeroSince = SAFE.zeroSince or now
else
if SAFE.zeroSince and now - SAFE.zeroSince > 20 then SAFE.start = now end
SAFE.zeroSince = nil
end
end
end
end)
local function SafeFilter(list)
local left = SafeLeft()
if left <= 0 then return list end
local folder = workspace:FindFirstChild("Titans")
if not folder then return list end
local allowed = SAFE.n - 1
local out, chosen, count = {}, {}, 0
for _, part in ipairs(list) do
local t = part
while t and t.Parent ~= folder do t = t.Parent end
if t then
if chosen[t] then
out[#out + 1] = part
elseif count < allowed then
chosen[t] = true
count = count + 1
out[#out + 1] = part
end
end
end
if #out < #list and os.clock() - SAFE.lastLog > 5 then
SAFE.lastLog = os.clock()
print(string.format("[SmoothHub][SafeTime] holding the last titan, %ds left (alive=%d)", math.ceil(left), SAFE.n))
end
return out
end
-- Safe Farm (Gas Mode): กดกระโดด 2 ครั้งทุก JUMP_EVERY วินาทีระหว่างฟาร์ม เพื่อให้แก๊สถูกใช้
-- PC = ปุ่ม Space, มือถือ = ปุ่มกระโดดบนจอของ Roblox
local JUMP_EVERY = 2 -- วินาทีที่กดค้างหลังการกดครั้งที่ 2
local JUMP_GAP = 0.9 -- วินาทีระหว่างการกดครั้งที่ 1 และ 2
local function FindJumpButton()
local touchGui = PlayerGui:FindFirstChild("TouchGui")
local frame = touchGui and touchGui:FindFirstChild("TouchControlFrame")
local btn = frame and frame:FindFirstChild("JumpButton")
if btn and btn:IsA("GuiObject") and btn.Visible and btn.AbsoluteSize.X > 0 then return btn end
return nil
end
local function PressJump()
local VIM = game:GetService("VirtualInputManager")
local btn = game:GetService("UserInputService").TouchEnabled and FindJumpButton() or nil
if btn then
local p = btn.AbsolutePosition + btn.AbsoluteSize / 2
local id = math.random(100000, 999999)
pcall(function() VIM:SendTouchEvent(id, 0, p.X, p.Y) end)
task.wait(0.05)
pcall(function() VIM:SendTouchEvent(id, 2, p.X, p.Y) end)
else
pcall(function() VIM:SendKeyEvent(true, Enum.KeyCode.Space, false, game) end)
task.wait(0.05)
pcall(function() VIM:SendKeyEvent(false, Enum.KeyCode.Space, false, game) end)
end
end
local jumpHeld = nil
local function JumpDown()
if jumpHeld then return end
local VIM = game:GetService("VirtualInputManager")
local btn = game:GetService("UserInputService").TouchEnabled and FindJumpButton() or nil
if btn then
local p = btn.AbsolutePosition + btn.AbsoluteSize / 2
local id = math.random(100000, 999999)
jumpHeld = { touch = true, id = id, x = p.X, y = p.Y }
pcall(function() VIM:SendTouchEvent(id, 0, p.X, p.Y) end)
else
jumpHeld = { touch = false }
pcall(function() VIM:SendKeyEvent(true, Enum.KeyCode.Space, false, game) end)
end
end
local function JumpUp()
local h = jumpHeld
if not h then return end
jumpHeld = nil
local VIM = game:GetService("VirtualInputManager")
if h.touch then
pcall(function() VIM:SendTouchEvent(h.id, 2, h.x, h.y) end)
else
pcall(function() VIM:SendKeyEvent(false, Enum.KeyCode.Space, false, game) end)
end
end
task.spawn(function()
local function active()
return Cfg.SafeFarm and Running() and B.Target
end
while Alive() do
if active() then
pcall(PressJump) -- กระโดดครั้งที่ 1 (แตะ)
task.wait(JUMP_GAP)
if active() then
pcall(JumpDown) -- กระโดดครั้งที่ 2 แล้วกดค้าง
local t0 = os.clock()
while os.clock() - t0 < JUMP_EVERY and active() do task.wait(0.05) end
pcall(JumpUp)
end
else
pcall(JumpUp)
task.wait(0.2)
end
end
pcall(JumpUp)
end)
local lastChar, readyAt = nil, 0
task.spawn(function()
local lastPick = 0
local orbitAngle = 0
while Alive() do
local dt = RunService.Heartbeat:Wait()
orbitAngle = (orbitAngle + ORBIT_SPEED * dt) % (math.pi * 2)
local root, char = MyRoot()
if char ~= lastChar then
lastChar = char
Farm.CharSpawnAt = os.clock()
readyAt = os.clock() + (Cfg.StartDelay or 0)
end
if Cfg.AutoFarm and Running() and Farm.BladesBusy and Farm.BladesBusy() then
B.Status = "Reloading"
if root then pcall(function() root.AssemblyLinearVelocity = Vector3.zero end) end
elseif not Running() then
B.Target = nil
if Cfg.AutoFarm or Cfg.AutoFarmThunder then B.Status = "Paused" else B.Status = "Idle" end
elseif not root then
B.Target = nil
B.Status = "Waiting for character"
elseif os.clock() < readyAt then
B.Status = "Start delay"
elseif Farm.BossBlock() then
B.Target = nil
B.Status = "Waiting for boss"
pcall(function() root.AssemblyLinearVelocity = Vector3.zero end)
else
if B.Target and not TitanAlive(B.Target) then
B.Target = nil
pcall(function() root.AssemblyLinearVelocity = Vector3.zero end)
end
if not B.Target and os.clock() - lastPick > 0.4 then
lastPick = os.clock()
B.Target = PickTitan(root)
end
local nape = B.Target and NapeOf(B.Target)
if nape then
local offset = Vector3.new(0, math.max(Cfg.FlyHeight or 150, 0), 0)
if ORBIT_ON then
offset = offset + Vector3.new(
math.cos(orbitAngle) * ORBIT_RADIUS,
math.sin(orbitAngle * 2) * ORBIT_BOB,
math.sin(orbitAngle) * ORBIT_RADIUS
)
end
local goal = nape.Position + offset
local diff = goal - root.Position
local dist = diff.Magnitude
pcall(function() root.AssemblyLinearVelocity = Vector3.zero end)
if dist > 1 then
local step = math.min((Cfg.FlySpeed or 600) * dt, dist)
local newPos = root.Position + diff.Unit * step
if ORBIT_ON and dist <= ARRIVE_DIST then
root.CFrame = CFrame.lookAt(newPos, Vector3.new(nape.Position.X, newPos.Y, nape.Position.Z))
else
root.CFrame = CFrame.new(newPos)
end
end
B.Status = (dist <= ARRIVE_DIST) and "Attacking" or "Flying to titan"
else
B.Target = nil
B.Status = "Searching for titans"
pcall(function() root.AssemblyLinearVelocity = Vector3.zero end)
end
end
end
end)
-- 💥 Burst Mode (ทำงานเมื่อเปิด Damage Multiplier): ยิงเป็นชุดละ BURST_SIZE ครั้งใส่ไททัน "เฉพาะตอนบินถึงตัวแล้ว"
-- แล้วพัก BURST_GAP วินาทีค่อยยิงชุดถัดไป (แทนการทยอยยิงทีละ 1 ใส่ไททัน 9 ตัวตลอดเวลา) | BURST_TARGETS = จำนวนไททันต่อชุด
local BURST_MODE = false -- Blades: กลับไปแบบเดิม (ฟันหลายตัวพร้อมกันต่อรอบ) | Thunder Spears ยิงเป็นชุดแยกด้านล่าง (SPEAR_BURST)
local BURST_SIZE = 10
local BURST_GAP = 0.1 -- ยิงชุดแรกทันทีที่ถึงตัว | ถ้าไททันยังไม่ตาย รอครบเวลานี้ก่อนยิงชุดถัดไป
local BURST_TARGETS = 10 -- โหมด Damage Multiplier: ยิงชุดละกี่ตัว | เดิม 1 (ถ้าปิงพุ่ง/กระตุก ลดเลขนี้ลง)
local burstAt = 0 -- เวลายิงชุดล่าสุด (ใช้ร่วมกันทุกตัว ไม่รอไททันตัวหลักเปลี่ยน/บินถึงตัวก่อน)
-- 🎯 Colossal Titan > Fake: ตอนมี Colossal ให้ยิงทุกชิ้นส่วน (BasePart) ในโฟลเดอร์ Fake ด้วย
local FAKE_ENABLED = false -- false = ปิดฟีเจอร์นี้
local FAKE_MAX_PARTS = 30 -- จำกัดจำนวนชิ้นต่อรอบ กัน remote ท่วม (ถ้า Fake มีชิ้นเยอะมาก)
local fakeLast = 0
local function FakeParts()
if not FAKE_ENABLED then return nil end
local folder = workspace:FindFirstChild("Titans")
local boss = folder and folder:FindFirstChild("Colossal_Titan")
if not (boss and boss:IsA("Model") and TitanAlive(boss)) then return nil end
local fake = boss:FindFirstChild("Fake")
if not fake then return nil end
local out = {}
if fake:IsA("BasePart") then out[1] = fake end
for _, d in ipairs(fake:GetDescendants()) do
if d:IsA("BasePart") and d.Parent then
out[#out + 1] = d
if #out >= FAKE_MAX_PARTS then break end
end
end
return #out > 0 and out or nil
end
local function SlashBurst(parts)
local remote = Post()
if not remote or NetPending() > NET_QUEUE_MAX then return false end
NetPush(remote, "Attacks", "Slash", true)
local total = 0
for _, part in ipairs(parts) do
local size = BURST_SIZE
if PRIORITY[part] and MultState.full > DAMAGE_MULT then
size = math.floor(BURST_SIZE * MultState.full / DAMAGE_MULT) -- Colossal: x30 = 3 เท่าของชุดปกติ
end
for _ = 1, size do
NetPush(remote, "Hitboxes", "Register", part, 143 + math.random() * 10, 0.26 + math.random() * 0.25)
end
total = total + size
end
NetPush(remote, "Attacks", "Slash", false)
B.Hits = B.Hits + total
return true
end
local StatsSvc = game:GetService("Stats")
local PING_BACKOFF = true -- ตั้งเป็น false ถ้าไม่อยากให้ลดความถี่ตามปิง
local PING_LIMIT = 350 -- ms: ปิงเกินนี้จะหน่วงช่วงโจมตีให้ห่างขึ้น
local function PingMs()
local ok, v = pcall(function() return StatsSvc.Network.ServerStatsItem["Data Ping"]:GetValue() end)
return (ok and tonumber(v)) or 0
end
task.spawn(function()
local partLast = setmetatable({}, { __mode = "k" })
while Alive() do
local gap = ATTACK_GAP[Cfg.AttackSpeed or 0] or 0.4
if PING_BACKOFF then
local ping = PingMs()
if ping > PING_LIMIT then gap = gap * math.clamp(ping / PING_LIMIT, 1, 4) end
end
task.wait(gap)
local target = B.Target
local root = MyRoot()
local bladesBusy = Farm.BladesBusy and Farm.BladesBusy()
if Cfg.AutoFarm and Running() and root and target and TitanAlive(target) and not bladesBusy and (B.Status == "Attacking" or (ATTACK_BEFORE_ARRIVE and B.Status == "Flying to titan")) then
local nape = NapeOf(target)
if nape then
local safeList = SafeFilter(NapesAround(target))
local burstOn = BURST_MODE and Cfg.DamageMultiplier
if burstOn then
if #safeList > 0 and os.clock() - burstAt >= BURST_GAP then
burstAt = os.clock()
local picked = {}
for i = 1, math.min(BURST_TARGETS, #safeList) do picked[i] = safeList[i] end
SlashBurst(picked)
end
elseif #safeList > 0 then
Slash(safeList)
end
if #safeList > 0 then
local fakeParts = FakeParts()
if fakeParts then
if burstOn then
if B.Status == "Attacking" and os.clock() - fakeLast >= BURST_GAP then
fakeLast = os.clock()
Slash(fakeParts)
end
else
Slash(fakeParts)
end
end
end
if Cfg.HitAllParts and #safeList > 0 and SafeLeft() <= 0 and not (burstOn and B.Status ~= "Attacking") then
local hit = HitParts(target)
local cd = math.max(Cfg.PartCooldown or 7, 4)
for _, name in ipairs(OTHER_PARTS) do
local part = hit and hit:FindFirstChild(name)
if part and part:IsA("BasePart") and os.clock() - (partLast[part] or 0) >= cd then
partLast[part] = os.clock()
Slash(part)
end
end
end
end
end
end
end)
task.spawn(function()
local SPEAR_BURST = true -- Thunder Spears: ยิงเป็นชุดทุก BURST_GAP วินาที (ไม่รัวต่อเนื่อง)
local spearAt = 0 -- เวลายิงชุดล่าสุด ใช้ร่วมกันทุกตัว เปลี่ยนเป้าหมายแล้วไม่ต้องรอใหม่
while Alive() do
task.wait(SPEAR_BURST and 0 or math.max(ATTACK_GAP[Cfg.AttackSpeed or 0] or 0.4, 0.6))
local target = B.Target
local root = MyRoot()
if Cfg.AutoFarmThunder and Running() and root and target and TitanAlive(target) and (B.Status == "Attacking" or (ATTACK_BEFORE_ARRIVE and B.Status == "Flying to titan"))
and (not SPEAR_BURST or os.clock() - spearAt >= 0.03) then
local safeList = SafeFilter(NapesAround(target, true))
if #safeList > 0 then
if SPEAR_BURST then
spearAt = os.clock()
local picked = {}
for i = 1, math.min(SpearLimit(), #safeList) do picked[i] = safeList[i] end
safeList = picked
end
Spear(safeList)
end
end
end
end)
-- ⚔️ Auto Clear Wave: ใช้ระบบโจมตีเดียวกับ Auto Farm [ Blades ] แต่ "ยืนอยู่กับที่" ไม่บินไปหาไททัน
-- โจมตีไททันที่อยู่ในระยะ (Cfg.ClearWaveRange) จากตำแหน่งปัจจุบัน | เปิดตัวนี้ = Auto Farm [ Blades ] / [ TS ] ถูกปิดให้อัตโนมัติ
Farm.ClearWaveStatus = "Off"
function Farm.OnAutoClearWave(on)
if on and SH.PlayOn() then
TurnOffOther("AutoFarm")
TurnOffOther("AutoFarmThunder")
else
Farm.ClearWaveStatus = "Off"
end
end
task.spawn(function()
local partLast = setmetatable({}, { __mode = "k" })
while Alive() do
task.wait(ATTACK_GAP[Cfg.AttackSpeed or 0] or 0.4)
if not (Cfg.AutoClearWave and SH.PlayOn()) then
Farm.ClearWaveStatus = (Cfg.AutoClearWave and "Waiting for Auto Play") or "Off"
elseif not Farm.CanUseHere() then
Farm.ClearWaveStatus = "Waiting for wave mode"
elseif Farm.BossBlock() then
Farm.ClearWaveStatus = "Waiting for boss"
elseif Farm.FailSafeActive then
Farm.ClearWaveStatus = "Paused (Fail Safe is losing on purpose)"
elseif Farm.Refilling then
Farm.ClearWaveStatus = "Paused (refilling)"
elseif Farm.BladesBusy and Farm.BladesBusy() then
Farm.ClearWaveStatus = "Reloading blades"
else
local root = MyRoot()
local folder = workspace:FindFirstChild("Titans")
if not root then
Farm.ClearWaveStatus = "Waiting for character"
elseif not folder then
Farm.ClearWaveStatus = "No titans yet"
else
local range = Cfg.ClearWaveRange or 300
local best, bestD, alive = nil, math.huge, 0
for _, t in ipairs(folder:GetChildren()) do
if t:IsA("Model") and TitanAlive(t) then
alive += 1
local nape = NapeOf(t)
if nape then
local d = (nape.Position - root.Position).Magnitude
if d <= range and d < bestD then best, bestD = t, d end
end
end
end
if not best then
Farm.ClearWaveStatus = (alive == 0) and "No titans alive" or ("No titan in range (%d alive)"):format(alive)
else
local list = {}
for _, part in ipairs(SafeFilter(NapesAround(best))) do
if (part.Position - root.Position).Magnitude <= range then list[#list + 1] = part end
end
if #list > 0 then
Slash(list)
Farm.ClearWaveStatus = ("Clearing: hitting %d titan(s) in range (%d alive)"):format(#list, alive)
if Cfg.HitAllParts and SafeLeft() <= 0 then
local hit = HitParts(best)
local cd = math.max(Cfg.PartCooldown or 7, 4)
for _, name in ipairs(OTHER_PARTS) do
local part = hit and hit:FindFirstChild(name)
if part and part:IsA("BasePart") and os.clock() - (partLast[part] or 0) >= cd
and (part.Position - root.Position).Magnitude <= range then
partLast[part] = os.clock()
Slash(part)
end
end
end
else
Farm.ClearWaveStatus = "Holding the last titan (Safe Time)"
end
end
end
end
end
end)
local ncParts, ncChar, ncAdded, ncStepped = {}, nil, nil, nil
ncStepped = RunService.Stepped:Connect(function()
if not Alive() then
if ncAdded then ncAdded:Disconnect() ncAdded = nil end
if ncStepped then ncStepped:Disconnect() end
return
end
if not Cfg.NoClip or not Running() then return end
local _, char = MyRoot()
if not char then return end
if char ~= ncChar then
ncChar = char
if ncAdded then ncAdded:Disconnect() end
ncParts = {}
for _, p in ipairs(char:GetDescendants()) do
if p:IsA("BasePart") then ncParts[#ncParts + 1] = p end
end
ncAdded = char.DescendantAdded:Connect(function(d)
if d:IsA("BasePart") then ncParts[#ncParts + 1] = d end
end)
end
for i = #ncParts, 1, -1 do
local p = ncParts[i]
if p.Parent then
if p.CanCollide then p.CanCollide = false end
else
table.remove(ncParts, i)
end
end
end)
end
do
local token = tostring(os.clock()) .. "_" .. tostring(math.random(100000, 999999))
local lastBeat, lastBeatAt = nil, 0
local function Get(name) return LocalPlayer:GetAttribute(name) end
local function Fresh()
local beat = Get("SH_Beat")
if beat == nil or Get("SH_ActorToken") ~= token then return false end
if beat ~= lastBeat then lastBeat, lastBeatAt = beat, os.clock() end
return os.clock() - lastBeatAt < 2
end
function Farm.ActorActive() return Fresh() end
local TRUST_ACTOR = false
function Farm.ActorSegments() if TRUST_ACTOR and Fresh() then return tonumber(Get("SH_Segments")) end return nil end
function Farm.ActorReloads() if TRUST_ACTOR and Fresh() then return tonumber(Get("SH_Reloads")) end return nil end
local CODE = [==[
local TOKEN = "__TOKEN__"
pcall(task.synchronize)
repeat task.wait() until game:IsLoaded()
local LP = game:GetService("Players").LocalPlayer
local Host
for _, c in ipairs(filtergc("table", { Keys = { "Modules", "Cache", "Invoked", "Services" } }, false)) do
if type(c) == "table" and type(c.Modules) == "table" and type(c.Cache) == "table"
and c.Cache.Player == LP and type(c.Modules.ODMG) == "table" then
Host = c
break
end
end
if not Host then return end
local beat, lastDump = 0, nil
local function Flat(t, depth, seen)
local parts = {}
if type(t) ~= "table" then return tostring(t) end
seen = seen or {}
if seen[t] then return "<cycle>" end
seen[t] = true
local keys = {}
for k in pairs(t) do keys[#keys + 1] = k end
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
for _, k in ipairs(keys) do
local v = t[k]
local tv = type(v)
if tv == "number" or tv == "string" or tv == "boolean" then
parts[#parts + 1] = tostring(k) .. "=" .. tostring(v)
elseif tv == "table" and depth > 0 then
parts[#parts + 1] = tostring(k) .. "={" .. Flat(v, depth - 1, seen) .. "}"
elseif tv == "table" then
parts[#parts + 1] = tostring(k) .. "={..}"
elseif tv == "function" then
parts[#parts + 1] = tostring(k) .. "()"
elseif tv == "userdata" or tv == "Instance" then
parts[#parts + 1] = tostring(k) .. "=<" .. typeof(v) .. ">"
end
end
return table.concat(parts, "; ")
end
local function Dump()
print("[SmoothHub][ActorDump] ---- begin ----")
print("[SmoothHub][ActorDump] Loadout: " .. Flat(Host.Loadout, 2))
print("[SmoothHub][ActorDump] Cache: " .. Flat(Host.Cache, 0))
if type(Host.Modules) == "table" then
local names = {}
for k, v in pairs(Host.Modules) do names[#names + 1] = tostring(k) .. "(" .. typeof(v) .. ")" end
table.sort(names)
print("[SmoothHub][ActorDump] Modules: " .. table.concat(names, ", "))
for _, k in ipairs({ "Spears", "Spear", "ThunderSpears", "ThunderSpear", "TS", "Weapons", "Weapon", "Combat", "Attacks" }) do
local m = Host.Modules[k]
if type(m) == "table" then print("[SmoothHub][ActorDump] Module " .. k .. ": " .. Flat(m, 0)) end
end
end
local ODMG = Host.Modules and Host.Modules.ODMG
if type(ODMG) == "table" then
print("[SmoothHub][ActorDump] ODMG: " .. Flat(ODMG, 0))
local ch = Host.Cache and Host.Cache.Character
if ch and type(ODMG.Rig) == "function" then
local ok, rig = pcall(ODMG.Rig, Host, ch)
print("[SmoothHub][ActorDump] Rig(ok=" .. tostring(ok) .. "): " .. (ok and Flat(rig, 2) or tostring(rig)))
end
end
print("[SmoothHub][ActorDump] ---- end ----")
end
while LP:GetAttribute("SH_ActorToken") == TOKEN do
task.wait(0.2)
local req = LP:GetAttribute("SH_DumpRequest")
if req ~= nil and req ~= lastDump then
lastDump = req
pcall(Dump)
end
local loadout = Host.Loadout
local supply = loadout and loadout.Supply
local seg = supply and tonumber(supply.Segments)
local rel = supply and tonumber(supply.Reloads)
beat = beat + 1
if type(supply) == "table" then
local parts = {}
for k, v in pairs(supply) do
local tv = type(v)
if tv == "number" or tv == "string" or tv == "boolean" then parts[#parts + 1] = tostring(k) .. "=" .. tostring(v) end
end
table.sort(parts)
LP:SetAttribute("SH_SupplyDump", table.concat(parts, "; "))
end
LP:SetAttribute("SH_Segments", seg)
LP:SetAttribute("SH_Reloads", rel)
LP:SetAttribute("SH_Beat", beat)
end
]==]
local function Supported()
return type(getactors) == "function" and type(run_on_actor) == "function" and type(filtergc) == "function"
end
local function Inject()
local okA, actors = pcall(getactors)
if not (okA and type(actors) == "table" and #actors > 0) then return false end
token = tostring(os.clock()) .. "_" .. tostring(math.random(100000, 999999))
LocalPlayer:SetAttribute("SH_ActorToken", token)
for _, actor in ipairs(actors) do
pcall(run_on_actor, actor, (CODE:gsub("__TOKEN__", function() return token end)))
end
return true
end
task.spawn(function()
if not Supported() then
print("[SmoothHub][Actor] executor ไม่รองรับ getactors/run_on_actor/filtergc -> ใช้วิธีเดิม")
return
end
local wasLinked = false
local lastTry = -999
while Alive() do
task.wait(0.5)
if Fresh() then
if not wasLinked then
wasLinked = true
print("[SmoothHub][Actor] เชื่อมกับระบบเกมสำเร็จ: Segments=" .. tostring(Get("SH_Segments")) .. " Reloads=" .. tostring(Get("SH_Reloads")))
end
else
if wasLinked then
wasLinked = false
print("[SmoothHub][Actor] สัญญาณหลุด (ตัวละครเกิดใหม่?) -> เชื่อมใหม่")
end
local char = LocalPlayer.Character
if char and os.clock() - lastTry >= 5 then
lastTry = os.clock()
Inject()
end
end
end
end)
task.spawn(function()
local last, lastAt = nil, 0
while Alive() do
task.wait(0.3)
local dump = LocalPlayer:GetAttribute("SH_SupplyDump")
local seg = tonumber(LocalPlayer:GetAttribute("SH_Segments"))
if Fresh() and dump and dump ~= last and (seg == nil or seg <= 2) and os.clock() - lastAt >= 1 then
last, lastAt = dump, os.clock()
print("[SmoothHub][Supply] " .. dump .. " | farm=" .. tostring(Farm.Blades and Farm.Blades.Status))
end
end
end)
task.spawn(function()
local n, last = 0, 0
while Alive() do
task.wait(0.5)
if Fresh() and n < 6 and os.clock() - last >= 4 and ((Farm.BladesEmpty and Farm.BladesEmpty()) or n == 0) then
n, last = n + 1, os.clock()
LocalPlayer:SetAttribute("SH_DumpRequest", n)
end
end
end)
task.spawn(function()
while Alive() do
task.wait(0.25)
pcall(function()
LocalPlayer:SetAttribute("SH_AutoReload", (Cfg.AutoReload and Farm.CanUseHere()) and true or false)
end)
end
pcall(function() LocalPlayer:SetAttribute("SH_ActorToken", nil) LocalPlayer:SetAttribute("SH_AutoReload", false) end)
end)
end
do
local RELOAD_WAIT = 1.5
local RELOAD_RETRY = 3
local remote = nil
Farm.LastReload = nil
local function BladeParts()
local char = LocalPlayer.Character
local rig = char and char:FindFirstChild("Rig_" .. LocalPlayer.Name)
local list = {}
for _, handName in ipairs({ "RightHand", "LeftHand" }) do
local hand = rig and rig:FindFirstChild(handName)
for _, d in ipairs(hand and hand:GetChildren() or {}) do
Loads code from the internet. It downloads more code when it runs (loadstring + HttpGet), which can change after it was posted. From: raw.githubusercontent.com.
Open Source

[UPDATE 5] Attack on Titan Revolution