Run it
Copy the script and paste it into your executor.
genv = ()
(genv genv[]) []
()()
(v) v
(v) v
(v) v
.PP_SCRAMBLE_STR = .PP_SCRAMBLE_STR PP_SCRAMBLE_STR
.PP_SCRAMBLE_NUM = .PP_SCRAMBLE_NUM PP_SCRAMBLE_NUM
.PP_SCRAMBLE_RE_NUM = .PP_SCRAMBLE_RE_NUM PP_SCRAMBLE_RE_NUM
= {}
.Lycoris = .Lycoris { silent = }
NO_HITBOX_ONLY =
NO_HITBOX_REACTION_BIAS_SECONDS = -
NO_HITBOX_SKILL_POLL_SECONDS =
NO_HITBOX_PART_POLL_SECONDS =
NO_HITBOX_MIN_BLOCK_SECONDS =
NO_HITBOX_ACTIVE_GUARD_PULSE_SECONDS =
activeSkillDef =
executeDefensiveAction = (_, _, actionFn)
(actionFn) ==
()
handleAutoCounterTiming = ()
Modules = {}
(name, factory)
Modules[name] = factory
(name)
module = Modules[name]
module ==
( .. (name))
(module) ==
result = ()
Modules[name] = result
result
module
(, ()
AttributeListener = {}
Maid = ()
Signal = ()
players = :()
()
+ Defender.()
attributeMaid = Maid.()
(character)
(character)
AttributeListener.()
localPlayer = players.LocalPlayer
character = localPlayer localPlayer.Character
character
fheld = character:()
fheld fheld:()
live = :()
liveChar = live localPlayer live:(localPlayer.Name)
fheld = liveChar liveChar:()
fheld fheld:()
fheld.Value ==
AttributeListener.()
localPlayer = players.LocalPlayer
characterAddedSignal = Signal.(localPlayer.CharacterAdded)
characterRemovingSignal = Signal.(localPlayer.CharacterRemoving)
attributeMaid:(characterAddedSignal:(, (character)
(character)
))
attributeMaid:(characterRemovingSignal:(, (character)
(character)
))
localPlayer.Character
(localPlayer.Character)
AttributeListener.()
attributeMaid:()
AttributeListener
)
(, ()
Signal = ()
Maid = ()
AnimatorDefender = ()
SoundDefender = ()
PartDefender = ()
PositionHistory = ()
Logger = ()
TaskSpawner = ()
Defense = {}
players = :()
runService = :()
workspaceService = :()
defenseMaid = Maid.()
liveMaid = Maid.()
defenderObjects = {}
defenderAnimationObjects = {}
defenderSoundObjects = {}
defenderPartObjects = {}
lastHistoryUpdate = .()
addAnimatorDefender = ((animator)
animationDefender = AnimatorDefender.(animator)
defenderObjects[animator] = animationDefender
defenderAnimationObjects[animator] = animationDefender
)
addSoundDefender = ((sound)
part = sound:()
part
soundDefender = SoundDefender.(sound, part)
defenderObjects[sound] = soundDefender
defenderSoundObjects[sound] = soundDefender
)
addPartDefender = ((part)
partDefender = PartDefender.(part)
partDefender
defenderObjects[part] = partDefender
defenderPartObjects[part] = partDefender
)
onLiveDescendantAdded = ((descendant)
descendant:()
(descendant)
descendant:()
(descendant)
)
(model)
model model:()
stands = workspaceService:()
stands model:(stands)
live = workspaceService:()
live
_, entity , live:()
standValue = entity:()
standValue standValue.Value standValue.Value:()
model == standValue.Value model:(standValue.Value)
onWorldDescendantAdded = ((descendant)
descendant:()
model = descendant:()
(model)
(descendant)
descendant:()
model = descendant:()
(model)
animator = descendant:()
animator
animator = .()
animator.Parent = descendant
(animator)
descendant:() (descendant)
_, child , descendant:()
child:()
(child)
child:()
animator = child:()
animator
animator = .()
animator.Parent = child
(animator)
descendant:()
(descendant)
)
onLiveDescendantRemoved = ((descendant)
object = defenderObjects[descendant]
object
object:()
defenderObjects[descendant] =
defenderAnimationObjects[descendant] =
defenderSoundObjects[descendant] =
defenderPartObjects[descendant] =
)
liveInstance =
()
_, object , defenderObjects
object:()
defenderObjects = {}
defenderAnimationObjects = {}
defenderSoundObjects = {}
defenderPartObjects = {}
(live)
liveInstance == live
liveInstance = live
liveMaid:()
()
live
liveMaid:(live.DescendantAdded:(onLiveDescendantAdded))
liveMaid:(live.DescendantRemoving:(onLiveDescendantRemoved))
_, descendant , live:()
(descendant)
updateHistory = (()
lastHistoryUpdate = .()
character = players.LocalPlayer players.LocalPlayer.Character
character
humanoidRootPart = character:()
humanoidRootPart
PositionHistory.(players.LocalPlayer, humanoidRootPart.CFrame, ())
_, player , players:()
player == players.LocalPlayer
pcharacter = player.Character
pcharacter
proot = pcharacter:()
proot
PositionHistory.(pcharacter, proot.CFrame, ())
)
updateDefenders = (()
_, object , defenderAnimationObjects
object object.update
object:()
_, object , defenderPartObjects
object object.update
object:()
)
Defense.()
renderStepped = Signal.(runService.RenderStepped)
postSimulation = Signal.(runService.PostSimulation)
defenseMaid:(renderStepped:(, updateHistory))
defenseMaid:(postSimulation:(, updateDefenders))
defenseMaid:(workspaceService.DescendantAdded:(onWorldDescendantAdded))
defenseMaid:(workspaceService.DescendantRemoving:(onLiveDescendantRemoved))
defenseMaid:(workspaceService.ChildAdded:((child)
child.Name ==
(child)
))
defenseMaid:(workspaceService.ChildRemoved:((child)
child == liveInstance
()
))
(workspaceService:())
_, descendant , workspaceService:()
(descendant)
Logger.()
Defense.()
liveMaid:()
()
defenseMaid:()
liveInstance =
Logger.()
Defense.()
_, object , defenderAnimationObjects
object object.blocking object:()
Defense
)
(, ()
Defender = ()
Signal = ()
SaveManager = ()
Logger = ()
InputClient = ()
Configuration = ()
PlaybackData = ()
M1AutoTrade = ()
Library = ()
workspaceService = :()
(aid)
Library Library.InfoLoggerData Library.InfoLoggerData.KeyBlacklistList
Library.InfoLoggerData.KeyBlacklistList[aid] ==
.isBlacklistedM1 = isBlacklistedM1
(model)
model model:()
stands = workspaceService:()
stands model:(stands)
live = workspaceService:()
live
_, entity , live:()
standValue = entity:()
standValue standValue.Value standValue.Value:()
model == standValue.Value model:(standValue.Value)
Maid = ()
RepeatInfo = ()
HitboxOptions = ()
Configuration = ()
PartTiming = ()
Action = ()
Task = ()
TaskSpawner = ()
PositionHistory = ()
OriginalStore = ()
runService = :()
hcCache = {}
hcCacheFrame =
runService.RenderStepped:((()
hcCacheFrame = hcCacheFrame +
hcCache = {}
))
(prefix, options, position, hitbox, shape, timing, action)
ent = options.entity options.part options.cframe
filter = options.filter
timingId = timing (timing._id timing.name)
actionId = action (action._type action.name)
actionWhen = action action._when
pos = position .zero
ptime = options.ptime
spredict = options.spredict
fhb = timing timing.fhb
duih = timing timing.duih
.(
,
prefix ,
(ent),
(filter),
(shape),
(timingId),
(actionId),
(actionWhen),
(spredict),
(fhb),
(duih),
ptime,
hitbox.X, hitbox.Y, hitbox.Z,
pos.X, pos.Y, pos.Z
)
AttributeListener = ()
AnimatorDefender = ({}, { __index = Defender })
AnimatorDefender.__index = AnimatorDefender
AnimatorDefender.__type =
players = :()
MAX_REPEAT_TIME =
HISTORY_STEPS =
PREDICT_FACING_DELTA =
AnimatorDefender.stopped = ((self, track, timing)
NO_HITBOX_ONLY
Configuration.()
timing.rpue
Random.():(, ) <= (Configuration.() )
, self:(timing, )
timing.iae track.IsPlaying
, self:(timing, )
timing.iae timing.ieae track.IsPlaying track.TimePosition < track.Length
, self:(timing, )
)
AnimatorDefender.rc = ((self, info)
info.track
Logger.(
,
(info.timing.name)
)
self:(info.track, info.timing)
info.timing.iae .() - info.start >= ((info.timing.mat / ) MAX_REPEAT_TIME)
self:(info.timing, )
)
AnimatorDefender.pfh = ((self, options)
yrate = PositionHistory.(self.entity)
yrate
root = self.entity:()
root
localRoot = players.LocalPlayer.Character players.LocalPlayer.Character:()
localRoot
.(yrate) < PREDICT_FACING_DELTA
clone = options:()
clone.spredict =
clone.hcolor = .(, , )
clone.mcolor = .(, , )
result =
store = OriginalStore.()
store:(root, , .(root.Position, localRoot.Position), ()
result = self:(clone, )
)
result
)
AnimatorDefender.phd = ((self, timing, options)
_, cframe , PositionHistory.(self.entity, HISTORY_STEPS, timing.phds) {}
clone = options:()
clone.spredict =
clone.cframe = cframe
clone.hcolor = .(, , )
clone.mcolor = .(, , )
self:(clone, )
)
AnimatorDefender.fsecs = ((self, timing)
player = players:(self.entity)
sd = (player player:() ) /
(timing.pfht ) + (sd + Defender.())
)
AnimatorDefender.fpc = ((self, timing, options)
timing.duih
timing.pfh self:(options)
timing.phd self:(timing, options)
)
AnimatorDefender.valid = ((self, timing, action)
Defender.(self, timing, action)
self.track
self:(timing, )
self.entity
self:(timing, )
target = self:(self.entity)
target
self:(timing, )
root = self.entity:()
root
self:(timing, )
self:(self.track, timing)
options = HitboxOptions.(root, timing)
options.spredict = timing.duih timing.dp
options.ptime = self:(timing)
options.action = action
options.entity = self.entity
info = RepeatInfo.(timing)
info.track = self.track
hc = self:(options, timing.duih info )
hc
pc = self:(timing, options)
pc
self:(timing, )
)
AnimatorDefender:(action, tp)
action.tp = tp
action[] = (action[])
action[] = (action[])
action[] = .(
(action[].X),
(action[].Y),
(action[].Z)
)
action[] ~=
action[] = (action[])
action[]
action[] = .(
(action[].X),
(action[].Y),
(action[].Z)
)
.(self.keyframes, action)
AnimatorDefender:()
self.track self.offset ==
self.track.TimePosition + ((self.offset + self.()) / self.track.Speed)
AnimatorDefender.latest = ((self)
latestKeyframe =
latestTimePosition =
_, keyframe , self.keyframes
(self:() ) <= keyframe.tp
latestTimePosition keyframe.tp <= latestTimePosition
latestTimePosition = keyframe.tp
latestKeyframe = keyframe
latestKeyframe
)
AnimatorDefender.update = ((self)
track, data , self.pbdata
Configuration.()
self.pbdata[track] =
track.IsPlaying
self.pbdata[track] =
self.rpbdata[(track.Animation.AnimationId)] = data
data:(track.Speed)
self.track self.timing
self.track.IsPlaying
latest = self:()
latest
tp = self:()
idx, keyframe , self.keyframes
tp <= keyframe.tp
self.keyframes[idx] =
self:(
self.timing,
,
tp,
self.track.TimePosition,
(latest.name),
(latest._type)
)
self.maid:(
TaskSpawner.(
.(, (latest._type)),
self.handle,
self,
self.timing,
latest,
)
)
)
AnimatorDefender:(track)
track.Priority == .AnimationPriority.Core
AnimatorDefender.process = ((self, track)
localCharacter = players.LocalPlayer.Character
isLocal = localCharacter self.entity == localCharacter
self:(track)
self.kfmaid:()
Configuration.()
self.pbdata[track] = PlaybackData.(self.entity)
aid = (track.Animation.AnimationId)
distance = self:(self.entity)
logAll = Configuration.()
logAll distance
distance =
ilr = logAll
(
distance
distance >= (Configuration.() )
distance <= (Configuration.() )
)
keyframeReached = Signal.(track.KeyframeReached)
self.kfmaid:(keyframeReached:(, (kfname)
ilr
Library:(distance, aid, kfname, track.TimePosition, )
))
timing = self:(self.entity, SaveManager.as, self.entity.Name, aid)
timing
isLocal
M1AutoTrade.(, track)
ilr
Library:(self.entity.Name, distance, timing)
(self.entity)
Configuration.() (aid)
InputClient.()
isLocal
M1AutoTrade.(timing, track)
(handleAutoCounterTiming) ==
(timing, , distance)
M1AutoTrade.(self, timing, distance)
Configuration.()
humanoidRootPart = self.entity:()
humanoidRootPart
self:()
self.timing = timing
self.track = track
self.offset = self.()
timing._id ==
dummyAction = { ihbc = }
self:(timing, dummyAction)
dist = distance self:(self.entity) .huge
dist <=
InputClient.()
InputClient.()
InputClient.()
.(, ()
InputClient.()
)
_, faction = (timing.actions._data)
NO_HITBOX_ONLY
Configuration.()
timing.rpue
timing.actions:() ==
Random.():(, ) <= (Configuration.() )
faction
(faction._type) ==
faction:() > (self.() + )
InputClient.()
self:(timing, )
timing.rpue
self:(timing)
self:(timing)
info = RepeatInfo.(timing, self.())
info.track = track
self:(Task.(.(, timing.name, ), ()
timing:() - info.irdelay - self.()
, timing.punishable, timing.after, self.rpue, self, self.entity, timing, info))
LRM_UserNote LRM_UserNote ==
self:(
timing,
,
(timing.name),
timing:(),
timing:(),
self.()
)
self:(
timing,
,
(timing.name),
self.()
)
)
AnimatorDefender:()
self.keyframes = {}
self.heffects = {}
self.kfmaid:()
Defender.(self)
AnimatorDefender.(animator)
entity = animator:()
entity
(.(, animator:()))
self = (Defender.(), AnimatorDefender)
animationPlayed = Signal.(animator.AnimationPlayed)
self.animator = animator
self.entity = entity
self.kfmaid = Maid.()
self.track =
self.timing =
self.rdelay =
self.heffects = {}
self.keyframes = {}
self.pbdata = {}
self.rpbdata = {}
self.maid:(animationPlayed:(
,
((track)
self:(track)
)
))
self
AnimatorDefender
)
(, ()
Logger = ()
Configuration = ()
Task = ()
Maid = ()
Library = ()
TaskSpawner = ()
Targeting = ()
PositionHistory = ()
HitboxOptions = ()
InputClient = ()
AttributeListener = ()
Keybinding = ()
OriginalStore = ()
Defender = {}
Defender.__index = Defender
Defender.__type =
stats = :()
userInputService = :()
players = :()
textChatService = :()
debrisService = :()
MAX_VISUALIZATION_TIME =
MAX_REPEAT_WAIT =
PREDICTION_LENIENCY_MULTI =
PREDICTION_EXTRA_SECONDS =
PREDICTION_MAX_SECONDS =
PREDICTION_SPEED_REF =
PREDICTION_MIN_SCALE =
PREDICTION_MAX_SCALE =
blockTagCounter =
defenseCooldowns = ({}, { __mode = })
(timing)
timing (timing.id) ==
ok, key = (timing.id, timing)
ok key
(key)
(timing timing.name )
()
blockTagCounter +=
.(, blockTagCounter)
Defender:(, key, name, distance, parent)
Configuration.()
distance < (Configuration.() )
distance > (Configuration.() )
Library:(, key, name, distance, parent)
Defender:(from)
from
entRootPart = from
from:()
entRootPart = from:()
entRootPart
localCharacter = players.LocalPlayer.Character
localCharacter
localRootPart = localCharacter:()
localRootPart
(entRootPart.Position - localRootPart.Position).Magnitude
Defender.target = ((self, entity)
Targeting.(entity)
)
Defender.rpue = ((self, entity, timing, info)
distance = self:(entity)
distance
Logger.(, (timing.name))
timing (distance < (timing.imdd) distance > (timing.imxd))
self:(timing, )
self:(info)
Logger.(
,
(timing.name)
)
target = self:(entity)
options = HitboxOptions.(.(), timing)
options.spredict =
options.part = target target.root
options.entity = entity
success = target self:(options, timing.duih info )
info.index = info.index +
self:(Task.(.(, (timing.name), info.index), ()
timing:() - info.irdelay - self.()
, timing.punishable, timing.after, self.rpue, self, self.entity, timing, info))
target
Logger.(, (timing.name))
success
Logger.(, (timing.name))
timing.srpn
self:(timing, , info.index)
InputClient.()
InputClient.()
)
Defender.valid = ((self, timing, action)
Configuration.()
Configuration.()
self:(timing, )
self:(timing, )
character = players.LocalPlayer.Character
character
self:(timing, )
integer = Random.():(, )
rate = Configuration.()
NO_HITBOX_ONLY Configuration.() integer <= rate
self:(timing, , integer, rate)
selectedFilters = Configuration.() {}
(name, legacy)
selectedFilters[name] (legacy selectedFilters[legacy])
currentState = character:()
() AttributeListener.krecently AttributeListener.()
self:(timing, )
() currentState ==
self:(timing, )
() currentState ==
self:(timing, )
currentState == currentState ==
self:(timing, )
chatInputBarConfiguration = textChatService:()
()
(userInputService:() (chatInputBarConfiguration chatInputBarConfiguration.IsFocused))
self:(timing, )
() iswindowactive ()
self:(timing, )
()
userInputService:((Keybinding.info Keybinding.info[]) .KeyCode.F)
self:(timing, )
timing.tag == ()
self:(timing, )
(timing.tag == timing.tag == ) (, )
self:(timing, )
(timing.tag == timing.tag == )
(, )
self:(timing, )
(timing.tag == timing.tag == )
(, )
self:(timing, )
)
Defender:(timing)
ms = Configuration.()
NO_HITBOX_ONLY ms <=
ms =
ms <=
entity = self.entity self.owner self.part
entity
key = (timing)
now = .()
bucket = defenseCooldowns[entity]
bucket
bucket = {}
defenseCooldowns[entity] = bucket
last = bucket[key]
last (now - last) < (ms / )
bucket[key] = now
(parts, filter)
_, part , parts
_, fpart , filter
part ~= fpart part:(fpart)
Defender.visualize = ((self, identifier, cframe, size, color, shape)
id = identifier self.hmaid:()
vpart = self.hmaid[id]
okParent, parent = ,
vpart
okParent, parent = (()
vpart.Parent
)
vpart ( okParent parent == )
(()
vpart:()
)
self.hmaid[id] =
vpart =
vpart = vpart .()
hitboxShape = shape ==
renderSize = size
hitboxShape ==
radius = .(size.X, size.Y, size.Z) /
renderSize = .(radius * , radius * , radius * )
vpart.Shape = .PartType.Ball
vpart.Shape = .PartType.Block
ok = (()
vpart.Parent =
)
ok
self.hmaid[id] =
vpart.Anchored =
vpart.CanCollide =
vpart.CanQuery =
vpart.CanTouch =
vpart.Material = .Material.ForceField
vpart.CastShadow =
vpart.Size = renderSize
vpart.CFrame = cframe
vpart.Color = color
vpart.Name = .(, id)
vpart.Transparency = Configuration.()
self.hmaid[id]
self.hmaid[id] = vpart
debrisService:(vpart, MAX_VISUALIZATION_TIME)
)
Defender.hitbox = ((self, cframe, fd, size, filter, shape)
shouldManualFilter = getexecutorname
(():() ():())
overlapParams = OverlapParams.()
overlapParams.FilterDescendantsInstances = shouldManualFilter {} filter
overlapParams.FilterType = shouldManualFilter .RaycastFilterType.Exclude .RaycastFilterType.Include
character = players.LocalPlayer.Character
character
,
root = character:()
root
,
usedCFrame = cframe
fd
usedCFrame = usedCFrame * .(, , -(size.Z / ))
parts =
hitboxShape = shape ==
hitboxShape ==
radius = .(size.X, size.Y, size.Z) /
parts = :(usedCFrame.Position, radius, overlapParams)
parts = :(usedCFrame, size, overlapParams)
shouldManualFilter (parts, filter) #parts > , usedCFrame
)
Defender.initial = ((self, from, pair, name, key)
timing = pair:(key)
distance = self:(from)
distance
timing (distance < (timing.imdd) distance > (timing.imxd))
timing
self:(self.__type, key, name, distance, from (from.Parent) )
timing
)
Defender.notify = ((self, timing, str, ...)
Configuration.()
Logger.(, (timing.name), self.__type, .(str, ...))
)
()
network = stats:()
network
serverStatsItem = network:()
serverStatsItem
dataPingItem = serverStatsItem:()
dataPingItem
ok, value = (()
dataPingItem:()
)
ok (value) ~=
value /
Defender.()
.(() / , )
Defender.()
.(() / , )
Defender.()
()
Defender.rc = ((self, info)
.() - info.start >= MAX_REPEAT_WAIT
)
Defender.duih = ((self, options, info)
clone = options:()
clone.hmid = self.hmaid:()
.()
self:(info)
self:(clone, )
)
Defender.hc = ((self, options, info)
action = options.action
timing = options.timing
character = players.LocalPlayer.Character
character
NO_HITBOX_ONLY
root = character:()
root
action action.ihbc
info
self:(options, info)
hitbox = options:()
shape = options:()
position = options:()
cacheKey = hcKey (, options, position, hitbox, shape, timing, action)
cached = cacheKey hcCache[cacheKey]
result, usedCFrame
cached cached.frame == hcCacheFrame
result = cached.result
usedCFrame = cached.usedCFrame
result, usedCFrame = self:(position, timing.fhb, hitbox, options.filter, shape)
cacheKey
hcCache[cacheKey] = { frame = hcCacheFrame, result = result, usedCFrame = usedCFrame }
usedCFrame
self:(options.hmid, usedCFrame, hitbox, options:(result), shape)
self:(options.hmid options.hmid + , root.CFrame, root.Size, options:(result), )
options.spredict result
result
closest = PositionHistory.(players.LocalPlayer, () - (self.() * PREDICTION_LENIENCY_MULTI))
closest
basePtime = options.ptime
basePtime <=
store = OriginalStore.()
extraSeconds = Configuration.()
(extraSeconds) ~=
extraSeconds = PREDICTION_EXTRA_SECONDS
speed = options.part options.part.AssemblyLinearVelocity options.part.AssemblyLinearVelocity.Magnitude
scale = .(speed / PREDICTION_SPEED_REF, PREDICTION_MIN_SCALE, PREDICTION_MAX_SCALE)
options.ptime = basePtime + (extraSeconds * scale)
options.ptime > PREDICTION_MAX_SECONDS
options.ptime = PREDICTION_MAX_SECONDS
eposition = options:()
predKey = hcKey (, options, eposition, hitbox, shape, timing, action)
predCached = predKey hcCache[predKey]
predCached predCached.frame == hcCacheFrame
result = predCached.result
usedCFrame = predCached.usedCFrame
store:(root, , closest, ()
result, usedCFrame = self:(eposition, timing.fhb, hitbox, options.filter, shape)
)
predKey
hcCache[predKey] = { frame = hcCacheFrame, result = result, usedCFrame = usedCFrame }
options.ptime = basePtime
usedCFrame
self:(options.hmid options.hmid + , usedCFrame, hitbox, options:(result), shape)
self:(options.hmid options.hmid + , root.CFrame, root.Size, options:(result), )
result
)
Defender.bend = ((self)
tagId = self.blockTagId ()
self.blockTagId = tagId
InputClient.(tagId, )
)
Defender.handle = ((self, timing, action, notify)
Configuration.()
self:(timing, action)
notify
self:(timing, , (action._type))
actionType = (action._type)
reactionDelay = NO_HITBOX_ONLY (Configuration.() )
reactionDelay > actionType ~= actionType ~=
.(reactionDelay)
dashReplacement = ( NO_HITBOX_ONLY)
(Random.():(, ) <= (Configuration.() ))
actionType ~=
dashReplacement =
Configuration.()
dashReplacement =
timing.actions:() ~=
dashReplacement =
actionType ==
tagId = self.blockTagId ()
self.blockTagId = tagId
InputClient.(tagId, , )
actionType ==
tagId = self.blockTagId ()
self.blockTagId = tagId
InputClient.(tagId, )
actionType ==
dashDir =
action.name
lname = .(action.name)
lname:(, , )
dashDir =
lname:(, , )
dashDir =
lname:(, , )
dashDir =
lname:(, , )
dashDir =
InputClient.(dashDir)
actionType ==
InputClient.()
dashReplacement timing.nfdb
self:(timing, )
InputClient.()
InputClient.()
)
Defender.blocking = ((self)
_, marker , self.markers
marker
_, , self.tasks
:()
)
Defender:()
self.tasks[#self.tasks + ] =
Defender:()
key, old , self.rhook
self[key]
self[key] = old
self.rhook = {}
Defender.clean = ((self)
self:()
self.tmaid:()
self.markers = {}
self.hmaid:()
stopBlock =
idx, , self.tasks
:()
self.tasks[idx] =
.identifier == .identifier ==
stopBlock =
stopBlock
tagId = self.blockTagId ()
self.blockTagId = tagId
InputClient.(tagId, )
)
Defender.module = ((self, timing, ...)
self:(timing, )
)
Defender.action = ((self, timing, action)
timing.umoa
action[] = (action[])
action[] = (action[])
action[] = (action[])
action[] = .(
(action[].X),
(action[].Y),
(action[].Z)
)
action[] ~=
action[] = (action[])
action[]
action[] = .(
(action[].X),
(action[].Y),
(action[].Z)
)
rdelay = self.()
self:(Task.((action._type), ()
action:() - rdelay - self.()
, timing.punishable, timing.after, self.handle, self, timing, action))
LRM_UserNote LRM_UserNote ==
self:(
timing,
,
(action.name),
action:(),
self.()
)
self:(
timing,
,
(action.name),
self.()
)
)
Defender.actions = ((self, timing)
Configuration.()
self:(timing)
_, action , timing.actions:()
self:(timing, action)
)
Defender:(key, new)
self.rhook[key]
Logger.(, key)
,
old = self[key]
(old) ~=
Logger.(, key)
,
self[key] = new
self.rhook[key] = old
Logger.(, key)
, old
Defender:()
self:()
self.maid:()
self =
Defender.()
self = ({}, Defender)
self.tasks = {}
self.rhook = {}
self.tmaid = Maid.()
self.maid = Maid.()
self.hmaid = Maid.()
self.markers = {}
self.lvisualization = .()
self.blockTagId = ()
self
Defender
)
(, ()
Defender = ()
SaveManager = ()
Signal = ()
Configuration = ()
RepeatInfo = ()
HitboxOptions = ()
SoundDefender = ({}, { __index = Defender })
SoundDefender.__index = SoundDefender
SoundDefender.__type =
players = :()
SoundDefender.valid = ((self, timing, action)
Defender.(self, timing, action)
self.owner self:(self.owner)
self:(timing, )
character = players.LocalPlayer.Character
if not character then
return self:notify(timing, "No character found.")
end
local options = HitboxOptions.new(self.part, timing, { character })
options.spredict = true
options.ptime = soundPredictionSeconds()
options.entity = self.owner or self.part
options.action = action
if not self:hc(options, timing.duih and RepeatInfo.new(timing) or nil) then
return self:notify(timing, "Not in hitbox.")
end
return true
end)
---Repeat conditional.
---@param self SoundDefender
---@param _ RepeatInfo
---@return boolean
SoundDefender.rc = LPH_NO_VIRTUALIZE(function(self, _)
if not self.sound.IsPlaying then
return false
end
return true
end)
---Process sound playing.
---@param self SoundDefender
SoundDefender.process = LPH_NO_VIRTUALIZE(function(self)
---@type SoundTiming?
local timing = self:initial(
self.owner or self.part,
SaveManager.ss,
self.owner and self.owner.Name or self.part.Name,
tostring(self.sound.SoundId)
)
if not timing then
return
end
local distance = self:distance(self.owner or self.part)
if type(handleAutoCounterTiming) == "function" then
handleAutoCounterTiming(timing, "Sound", distance)
end
if not Configuration.expectToggleValue("EnableAutoDefense") then
return
end
if players.LocalPlayer.Character and self.owner == players.LocalPlayer.Character then
return
end
---@note: Clean up previous tasks that are still waiting or suspended because they're in a different track.
self:clean()
-- Add actions.
return self:actions(timing)
end)
---Create new SoundDefender object.
---@param sound Sound
---@param part BasePart
---@return SoundDefender
function SoundDefender.new(sound, part)
local self = setmetatable(Defender.new(), SoundDefender)
local soundPlayed = Signal.new(sound.Played)
self.sound = sound
self.part = part
self.owner = sound:FindFirstAncestorWhichIsA("Model")
self.maid:mark(soundPlayed:connect(
"SoundDefender_OnSoundPlayed",
LPH_NO_VIRTUALIZE(function()
self:process()
end)
))
if sound.Playing then
self:process()
end
return self
end
-- Return SoundDefender module.
return SoundDefender
end)
-- MODULE: Features/Combat/Objects/PartDefender
defineModule("Features/Combat/Objects/PartDefender", function()
---@module Features.Combat.Objects.Defender
local Defender = require("Features/Combat/Objects/Defender")
---@module Game.Timings.SaveManager
local SaveManager = require("Game/Timings/SaveManager")
---@module Features.Combat.Objects.RepeatInfo
local RepeatInfo = require("Features/Combat/Objects/RepeatInfo")
---@module Features.Combat.Objects.HitboxOptions
local HitboxOptions = require("Features/Combat/Objects/HitboxOptions")
---@class PartDefender: Defender
---@field part BasePart
---@field timing PartTiming
---@field touched boolean Determines whether if we touched the timing in the past.
local PartDefender = setmetatable({}, { __index = Defender })
PartDefender.__index = PartDefender
PartDefender.__type = "Part"
-- Services.
local players = game:GetService("Players")
local HIT_COLOR = Color3.new(0, 1, 0)
local MISS_COLOR = Color3.new(1, 0, 0)
local exclusiveUntil = {}
local function partPredictionSeconds()
return 0.15 + Defender.rdelay()
end
local function applyWorldOffset(cframe, offset)
if not offset or offset == Vector3.zero then
return cframe
end
local rotation = cframe - cframe.Position
local applied = (cframe.RightVector * offset.X)
+ (cframe.UpVector * offset.Y)
+ (cframe.LookVector * offset.Z)
return CFrame.new(cframe.Position + applied) * rotation
end
local function normalizeCharName(value)
return string.lower(tostring(value or "")):gsub("[^%w]", "")
end
local function getLastLoadedChar(player)
if not player then
return nil
end
local raw = player:GetAttribute("LastLoadedChar")
if raw ~= nil then
return raw
end
local character = player.Character
if character then
return character:GetAttribute("LastLoadedChar")
end
return nil
end
local function matchesCharRequirement(part, timing)
if not timing.pchar or timing.pchar == "" then
return true
end
local directChar = part:GetAttribute("LastLoadedChar")
if not directChar then
local parent = part.Parent
if parent then
directChar = parent:GetAttribute("LastLoadedChar")
end
end
if not directChar then
local modelAttr = part:FindFirstAncestorWhichIsA("Model")
if modelAttr then
directChar = modelAttr:GetAttribute("LastLoadedChar")
end
end
if directChar then
return normalizeCharName(directChar) == normalizeCharName(timing.pchar)
end
local owner = nil
local model = part:FindFirstAncestorWhichIsA("Model")
if model then
owner = players:GetPlayerFromCharacter(model)
end
local creator = part:FindFirstChild("creator") or part:FindFirstChild("Creator")
if not owner and creator and creator.Value and creator.Value:IsA("Player") then
owner = creator.Value
end
local ownerAttr = part:GetAttribute("Owner")
if not owner and typeof(ownerAttr) == "Instance" and ownerAttr:IsA("Player") then
owner = ownerAttr
end
local ownerId = part:GetAttribute("OwnerUserId")
if not owner and type(ownerId) == "number" then
owner = players:GetPlayerByUserId(ownerId)
end
if not owner then
local closest = nil
local closestDist = math.huge
local targetNorm = normalizeCharName(timing.pchar)
for _, plr in ipairs(players:GetPlayers()) do
if plr == players.LocalPlayer then
continue
end
local raw = getLastLoadedChar(plr)
if raw and normalizeCharName(raw) == targetNorm then
local char = plr.Character
local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso"))
if root then
local dist = (root.Position - part.Position).Magnitude
if dist < closestDist then
closestDist = dist
closest = plr
end
end
end
end
owner = closest
end
if owner == players.LocalPlayer then
return false
end
local raw = getLastLoadedChar(owner)
if not raw then
return false
end
if raw == timing.pchar then
return true
end
return normalizeCharName(raw) == normalizeCharName(timing.pchar)
end
local function matchesColorRequirement(part, timing)
if not timing.pbrick and not timing.prgb then
return true
end
if timing.pbrick then
local partBrick = part.BrickColor and part.BrickColor.Name or nil
if not partBrick or string.lower(partBrick) ~= string.lower(timing.pbrick) then
return false
end
end
if timing.prgb then
local r = timing.prgb.r or timing.prgb.R or timing.prgb[1]
local g = timing.prgb.g or timing.prgb.G or timing.prgb[2]
local b = timing.prgb.b or timing.prgb.B or timing.prgb[3]
if type(r) ~= "number" or type(g) ~= "number" or type(b) ~= "number" then
return false
end
local color = part.Color
if not color then
return false
end
local pr = math.floor(color.R * 255 + 0.5)
local pg = math.floor(color.G * 255 + 0.5)
local pb = math.floor(color.B * 255 + 0.5)
if math.abs(pr - r) > 1 or math.abs(pg - g) > 1 or math.abs(pb - b) > 1 then
return false
end
end
return true
end
local function timingSpecificity(timing)
local score = 0
if timing.pbrick then
score += 1
end
if timing.prgb then
score += 2
end
if timing.pchar then
score += 3
end
return score
end
local function matchesTiming(part, timing)
local pname = timing.pname
local partName = string.lower(part.Name)
if pname and pname ~= "" then
if string.lower(pname) ~= partName then
return false
end
elseif string.lower(timing.name) ~= partName then
return false
end
if not matchesColorRequirement(part, timing) then
return false
end
if not matchesCharRequirement(part, timing) then
return false
end
return true
end
local function resolveTiming(part)
local best = nil
local bestScore = -1
for _, timing in ipairs(SaveManager.ps:list()) do
if not matchesTiming(part, timing) then
continue
end
local score = timingSpecificity(timing)
if score > bestScore then
bestScore = score
best = timing
end
end
return best
end
local function exclusiveKey(timing)
if timing.pname and timing.pname ~= "" then
return timing.pname
end
return timing.name or ""
end
local function exclusiveActive(timing)
local seconds = timing.exclusiveSeconds or 0
if seconds <= 0 then
return false
end
local key = exclusiveKey(timing)
local untilTime = exclusiveUntil[key]
if not untilTime then
return false
end
if os.clock() >= untilTime then
exclusiveUntil[key] = nil
return false
end
return true
end
local function markExclusive(timing)
local seconds = timing.exclusiveSeconds or 0
if seconds <= 0 then
return
end
local key = exclusiveKey(timing)
exclusiveUntil[key] = os.clock() + seconds
end
---Get CFrame.
---@param self PartDefender
---@return CFrame
PartDefender.cframe = LPH_NO_VIRTUALIZE(function(self)
return self.timing.uhc and self.part.CFrame or CFrame.new(self.part.Position)
end)
---Check if we're in a valid state to proceed with the action.
---@param self PartDefender
---@param timing PartTiming
---@param action Action
---@return boolean
PartDefender.valid = LPH_NO_VIRTUALIZE(function(self, timing, action)
if not Defender.valid(self, timing, action) then
return false
end
if exclusiveActive(timing) then
return false
end
local character = players.LocalPlayer.Character
if not character then
return self:notify(timing, "No character found.")
end
local options = HitboxOptions.new(self.part, timing, { character })
options.spredict = true
options.ptime = partPredictionSeconds()
options.entity = self.entity or self.part
options.action = action
if self.hmid then
options.hmid = self.hmid
end
if not self.timing.duih then
if not self:hc(options, timing.duih and RepeatInfo.new(timing) or nil) then
return self:notify(timing, "Not in hitbox.")
end
end
return true
end)
---Update PartDefender object.
---@param self PartDefender
PartDefender.update = LPH_NO_VIRTUALIZE(function(self)
if not self.timing.duih then
return
end
if exclusiveActive(self.timing) then
return
end
if #self.tasks > 0 then
return
end
local localPlayer = players.LocalPlayer
if not localPlayer then
return
end
local character = localPlayer.Character
if not character then
return
end
local hb = self.timing.hitbox
hb = Vector3.new(PP_SCRAMBLE_NUM(hb.X), PP_SCRAMBLE_NUM(hb.Y), PP_SCRAMBLE_NUM(hb.Z))
local shape = self.timing.shape and PP_SCRAMBLE_STR(self.timing.shape) or "Box"
local options = HitboxOptions.new(self.part, self.timing, { character })
options.spredict = true
options.ptime = partPredictionSeconds()
options.entity = self.entity or self.part
if not self.hmid then
self.hmid = self.hmaid:uid()
end
options.hmid = self.hmid
local touching = self:hc(options, nil)
if not touching then
return
end
if self.touched and touching then
return
end
self.touched = touching
self:clean()
markExclusive(self.timing)
if type(handleAutoCounterTiming) == "function" then
handleAutoCounterTiming(self.timing, "Part", self:distance(self.part))
end
return self:actions(self.timing)
end)
function PartDefender:detach()
Defender.detach(self)
end
---Create new PartDefender object.
---@param part BasePart
---@param timing PartTiming?
---@return PartDefender?
function PartDefender.new(part, timing)
local self = setmetatable(Defender.new(), PartDefender)
self.part = part
self.entity = part:FindFirstAncestorWhichIsA("Model")
local selected = timing or resolveTiming(part)
if not selected then
self:initial(part, SaveManager.ps, nil, part.Name)
return nil
end
local distance = self:distance(part)
if not distance then
return nil
end
if distance < PP_SCRAMBLE_NUM(selected.imdd) or distance > PP_SCRAMBLE_NUM(selected.imxd) then
return nil
end
self.timing = selected
self.touched = false
if not self.timing then
return nil
end
if self.timing.umoa then
self:module(self.timing)
end
if type(handleAutoCounterTiming) == "function" then
handleAutoCounterTiming(self.timing, "Part", distance)
end
if not self.timing.umoa and not self.timing.duih then
self:actions(self.timing)
end
return self
end
-- Return PartDefender module.
return PartDefender
end)
-- MODULE: Features/Combat/Objects/HitboxOptions
defineModule("Features/Combat/Objects/HitboxOptions", function()
---@class HitboxOptions
---@note: Options for the hitbox check.
---@field part BasePart? If this is specified and it exists, it will be used for the position.
---@field cframe CFrame? Else, the part's CFrame will be used.
---@field timing Timing|AnimationTiming|SoundTiming
---@field action Action?
---@field filter Instance[]
---@field spredict boolean If true, a check will run for predicted positions.
---@field ptime number? The predicted time in seconds for extrapolation.
---@field entity Model? The entity for extrapolation.
---@field phcolor Color3 The color for predicted hitboxes.
---@field pmcolor Color3 The color for predicted missed hitboxes.
---@field hcolor Color3 The color for hitboxes.
---@field mcolor Color3 The color for missed hitboxes.
---@field hmid number? Hitbox visualization ID for normal hitbox check.
local HitboxOptions = {}
HitboxOptions.__index = HitboxOptions
-- Services.
local players = game:GetService("Players")
local function applyHitboxWorldOffset(cframe, offset)
if not offset or offset == Vector3.zero then
return cframe
end
local rotation = cframe - cframe.Position
local applied = (cframe.RightVector * offset.X)
+ (cframe.UpVector * offset.Y)
+ (cframe.LookVector * offset.Z)
return CFrame.new(cframe.Position + applied) * rotation
end
---Hit color.
---@return Color3
function HitboxOptions:ghcolor(result)
return result and self.hcolor or self.mcolor
end
---Predicted hit color.
---@return Color3
function HitboxOptions:gphcolor(result)
return result and self.phcolor or self.pmcolor
end
---Cloned hitbox options.
---@return HitboxOptions
function HitboxOptions:clone()
local options = setmetatable({}, HitboxOptions)
options.action = self.action
options.spredict = self.spredict
options.entity = self.entity
options.ptime = self.ptime
options.entity = self.entity
options.phcolor = self.phcolor
options.pmcolor = self.pmcolor
options.hcolor = self.hcolor
options.mcolor = self.mcolor
options.hmid = self.hmid
options.filter = self.filter
options.part = self.part
options.cframe = self.cframe
options.timing = self.timing
return options
end
---Get the hitbox shape.
---@return string
function HitboxOptions:shape()
local shape = self.action and self.action.shape or self.timing.shape
if self.timing.duih then
shape = self.timing.shape
end
if typeof(shape) == "string" then
shape = PP_SCRAMBLE_STR(shape)
end
return shape == "Ball" and "Ball" or "Box"
end
---Get the hitbox size.
---@return Vector3
function HitboxOptions:hitbox()
local hitbox = self.action and self.action.hitbox or self.timing.hitbox
if self.timing.duih then
hitbox = self.timing.hitbox
end
hitbox = Vector3.new(PP_SCRAMBLE_NUM(hitbox.X), PP_SCRAMBLE_NUM(hitbox.Y), PP_SCRAMBLE_NUM(hitbox.Z))
return hitbox
end
---Get the hitbox offset.
---@return Vector3
function HitboxOptions:offset()
local offset = self.action and self.action.offset or self.timing.offset
if self.timing.duih then
offset = self.timing.offset
end
if typeof(offset) ~= "Vector3" then
return Vector3.zero
end
return Vector3.new(PP_SCRAMBLE_NUM(offset.X), PP_SCRAMBLE_NUM(offset.Y), PP_SCRAMBLE_NUM(offset.Z))
end
---Get extrapolated position.
---@return CFrame
HitboxOptions.extrapolate = LPH_NO_VIRTUALIZE(function(self)
if not self.part then
return error("HitboxOptions.extrapolate - unimplemented for CFrame")
end
if not self.entity then
return error("HitboxOptions.extrapolate - no entity specified")
end
if not self.ptime then
return error("HitboxOptions.extrapolate - no predicted time specified")
end
-- Return the extrapolated position.
local cframe = self.part.CFrame + (self.part.AssemblyLinearVelocity * self.ptime)
return applyHitboxWorldOffset(cframe, self:offset())
end)
---Get position.
---@return CFrame
HitboxOptions.pos = LPH_NO_VIRTUALIZE(function(self)
local cframe = nil
if self.cframe then
cframe = self.cframe
end
if self.part then
cframe = self.part.CFrame
end
if not cframe then
return error("HitboxOptions.pos - impossible condition")
end
return applyHitboxWorldOffset(cframe, self:offset())
end)
---Create new HitboxOptions object.
---@param target Instance|CFrame
---@param timing Timing|AnimationTiming|SoundTiming
---@param filter Instance[]?
---@return HitboxOptions
HitboxOptions.new = LPH_NO_VIRTUALIZE(function(target, timing, filter)
local self = setmetatable({}, HitboxOptions)
self.part = typeof(target) == "Instance" and target:IsA("BasePart") and target
self.cframe = typeof(target) == "CFrame" and target
self.timing = timing
self.action = nil
self.filter = filter or {}
self.spredict = false
self.hmid = nil
self.entity = nil
self.phcolor = Color3.new(1, 0, 1)
self.pmcolor = Color3.new(0.349019, 0.345098, 0.345098)
self.hcolor = Color3.new(0, 1, 0)
self.mcolor = Color3.new(1, 0, 0)
self.ptime = nil
if not self.part and not self.cframe then
return error("HitboxOptions: No part or CFrame specified.")
end
if filter then
return self
end
local character = players.LocalPlayer.Character
if not character then
return self
end
self.filter = { character }
return self
end)
-- Return HitboxOptions module.
return HitboxOptions
end)
-- MODULE: Features/Combat/Objects/RepeatInfo
defineModule("Features/Combat/Objects/RepeatInfo", function()
---@note: Typed object that represents information. It's not really a true class but just needs to store the correct data.
---@class RepeatInfo
---@field track AnimationTrack?
---@field timing Timing
---@field start number
---@field index number
---@field irdelay number Initial receive delay.
local RepeatInfo = {}
RepeatInfo.__index = RepeatInfo
---Create new RepeatInfo object.
---@param timing Timing
---@param irdelay number
---@return RepeatInfo
function RepeatInfo.new(timing, irdelay)
local self = setmetatable({}, RepeatInfo)
self.track = nil
self.timing = timing
self.start = os.clock()
self.index = 0
self.irdelay = irdelay
return self
end
-- Return RepeatInfo module.
return RepeatInfo
end)
-- MODULE: Features/Combat/Objects/Target
defineModule("Features/Combat/Objects/Target", function()
---@note: Typed object that represents a target. It's not really a true class but just needs to store the correct data.
---@class Target
---@field character Model
---@field humanoid Humanoid
---@field root BasePart
---@field dc number Distance to crosshair.
---@field fov number Field of view to target.
---@field du number Distance to us.
local Target = {}
---Create new Target object.
---@param character Model
---@param humanoid Humanoid
---@param root BasePart
---@param dc number
---@param fov number
---@param du number
---@return Target
function Target.new(character, humanoid, root, dc, fov, du)
local self = setmetatable({}, Target)
self.character = character
self.humanoid = humanoid
self.root = root
self.dc = dc
self.fov = fov
self.du = du
return self
end
-- Return Target module.
return Target
end)
-- MODULE: Features/Combat/Objects/Task
defineModule("Features/Combat/Objects/Task", function()
---@module Utility.TaskSpawner
local TaskSpawner = require("Utility/TaskSpawner")
---@module Utility.Configuration
local Configuration = require("Utility/Configuration")
---@class Task
---@field thread thread
---@field identifier string
---@field _when number A timestamp when the task will be executed.
---@field punishable number A window in seconds where the task can be punished.
---@field after number A window in seconds where the task can be executed.
---@field delay function
local Task = {}
Task.__index = Task
---Check if task should block the input.
---@return boolean
function Task:blocking()
if not (coroutine.status(self.thread) ~= "dead") then
return false
end
-- We've exceeded the execution time. Block if we're within the after window.
local whenAt = Task.when(self)
if os.clock() >= whenAt then
return os.clock() <= whenAt + self.after
end
---@note: Allow us to do inputs up until a certain amount of time before the task happens.
return os.clock() >= whenAt - self.punishable
end
---Cancel task.
function Task:cancel()
if coroutine.status(self.thread) ~= "suspended" then
return
end
task.cancel(self.thread)
end
---Get when approximately the task will be executed.
---@return number
function Task:when()
local base = self._when
if type(base) ~= "number" then
local legacy = self.when
if type(legacy) == "number" then
base = legacy
else
base = 0
end
end
local delay = self.delay
if type(delay) == "function" then
return base + delay()
end
return base + (delay or 0)
end
---Create new Task object.
---@param identifier string
---@param delay function
---@param punishable number
---@param after number
---@param callback function
---@vararg any
---@return Task
function Task.new(identifier, delay, punishable, after, callback, ...)
local self = setmetatable({}, Task)
self.identifier = identifier
self._when = os.clock()
self.delay = delay
self.punishable = punishable
self.after = after
self.thread = TaskSpawner.delay("Action_" .. identifier, delay, callback, ...)
if not self.punishable or self.punishable <= 0 then
self.punishable = Configuration.expectOptionValue("DefaultPunishableWindow") or 0.7
end
if not self.after or self.after <= 0 then
self.after = Configuration.expectOptionValue("DefaultAfterWindow") or 0.1
end
return self
end
-- Return Task module.
return Task
end)
-- MODULE: Features/Combat/PositionHistory
defineModule("Features/Combat/PositionHistory", function()
-- PositionHistory module.
local PositionHistory = {}
-- Histories table.
local histories = {}
-- Max history seconds.
local MAX_HISTORY_SECS = 3.0
---Add an entry to the history list.
---@param idx any
---@param position CFrame
---@param timestamp number
function PositionHistory.add(idx, position, timestamp)
local history = histories[idx] or {}
if not histories[idx] then
histories[idx] = history
end
history[#history + 1] = {
position = position,
timestamp = timestamp,
}
while true do
local tail = history[1]
if not tail then
break
end
if tick() - tail.timestamp <= MAX_HISTORY_SECS then
break
end
table.remove(history, 1)
end
end
---Get the horizontal angular velocity (yaw rate) for a current index.
---@param index any
---@return number?
function PositionHistory.yrate(index)
local history = histories[index]
if not history or #history < 2 then
return nil
end
local latest = history[#history]
local previous = history[#history - 1]
local dt = latest.timestamp - previous.timestamp
if dt <= 1e-4 then
return nil
end
local prevLook = Vector3.new(previous.position.LookVector.X, 0, previous.position.LookVector.Z).Unit
local latestLook = Vector3.new(latest.position.LookVector.X, 0, latest.position.LookVector.Z).Unit
local dot = prevLook:Dot(latestLook)
local crossY = prevLook:Cross(latestLook).Y
local angle = math.atan2(crossY, dot)
return angle / dt
end
---Divides the history into a number of equal steps and returns the position at each step.
---@param idx any
---@param steps number
---@param phds number History second limit for past hitbox detection.
---@return CFrame[]?
function PositionHistory.stepped(idx, steps, phds)
local history = histories[idx]
if not history or #history == 0 then
return nil
end
if not steps or steps <= 0 then
return nil
end
local vhistory = {}
local vhtime = history[#history].timestamp
for _, data in next, history do
if vhtime - data.timestamp > phds then
continue
end
vhistory[#vhistory + 1] = data.position
end
if #vhistory == 0 then
return {}
end
local count = math.min(steps, #vhistory)
local out = table.create(count)
for cidx = 1, count do
out[cidx] = vhistory[math.max(math.floor((cidx * #vhistory) / count), 1)]
end
return out
end
---Get closest position (in time) to a timestamp.
---@param idx any
---@param timestamp number
---@return CFrame?
function PositionHistory.closest(idx, timestamp)
if not histories[idx] then
return nil
end
local closestDelta = nil
local closestPosition = nil
for _, data in next, histories[idx] do
local delta = math.abs(timestamp - data.timestamp)
if closestDelta and delta >= closestDelta then
continue
end
closestPosition = data.position
closestDelta = delta
end
return closestPosition
end
-- Return PositionHistory module.
return PositionHistory
end)
-- MODULE: Features/Combat/Targeting
defineModule("Features/Combat/Targeting", function()
-- Targeting module.
---@note: Glorified extended non-utility Entities file.
local Targeting = {}
---@module Utility.Configuration
local Configuration = require("Utility/Configuration")
---@module Features.Combat.Objects.Target
local Target = require("Features/Combat/Objects/Target")
---@module Utility.Table
local Table = require("Utility/Table")
-- Services.
local players = game:GetService("Players")
local userInputService = game:GetService("UserInputService")
local targetCache = {
lastAt = 0,
lastMouse = nil,
lastLocalPos = nil,
lastCamCFrame = nil,
targets = nil,
sortType = nil,
maxTargets = nil,
}
local function shouldRebuildTargets(localRootPart, currentCamera, sortType, maxTargets)
if not targetCache.targets then
return true
end
local now = os.clock()
if now - (targetCache.lastAt or 0) > 0.05 then
return true
end
if targetCache.sortType ~= sortType or targetCache.maxTargets ~= maxTargets then
return true
end
local mousePos = userInputService:GetMouseLocation()
local lastMouse = targetCache.lastMouse
if not lastMouse then
return true
end
local dx = mousePos.X - lastMouse.X
local dy = mousePos.Y - lastMouse.Y
if (dx * dx + dy * dy) > 4 then
return true
end
if not localRootPart then
return true
end
local lastPos = targetCache.lastLocalPos
if not lastPos or (localRootPart.Position - lastPos).Magnitude > 2 then
return true
end
if not currentCamera then
return true
end
local lastCam = targetCache.lastCamCFrame
if not lastCam then
return true
end
local camCFrame = currentCamera.CFrame
if (camCFrame.Position - lastCam.Position).Magnitude > 0.5 then
return true
end
if camCFrame.LookVector:Dot(lastCam.LookVector) < 0.999 then
return true
end
return false
end
local function isAlly(player)
local localPlayer = players.LocalPlayer
if not localPlayer or not player then
return false
end
local ok, status = pcall(localPlayer.GetFriendStatus, localPlayer, player)
if not ok then
return false
end
return status == Enum.FriendStatus.Friend
end
---Get a list of all viable targets.
---@return Target[]
Targeting.viable = LPH_NO_VIRTUALIZE(function()
local ents = workspace:FindFirstChild("Live")
if not ents then
return {}
end
local localCharacter = players.LocalPlayer.Character
if not localCharacter then
return {}
end
local localRootPart = localCharacter and localCharacter:FindFirstChild("HumanoidRootPart")
if not localRootPart then
return {}
end
local currentCamera = workspace.CurrentCamera
if not currentCamera then
return {}
end
local targets = {}
for _, entity in next, ents:GetChildren() do
if entity == localCharacter then
continue
end
local playerFromCharacter = players:GetPlayerFromCharacter(entity)
if not playerFromCharacter then
continue
end
if Configuration.expectToggleValue("IgnorePlayers") then
continue
end
local humanoid = entity:FindFirstChildWhichIsA("Humanoid")
if not humanoid then
continue
end
local rootPart = entity:FindFirstChild("HumanoidRootPart")
if not rootPart then
continue
end
if humanoid.Health <= 0 then
continue
end
if playerFromCharacter then
local usernameList = Options["UsernameList"]
local displayNameFound = table.find(usernameList.Values, playerFromCharacter.DisplayName)
local usernameFound = table.find(usernameList.Values, playerFromCharacter.Name)
if displayNameFound or usernameFound then
continue
end
end
local fieldOfViewToEntity =
currentCamera.CFrame.LookVector:Dot((localRootPart.Position - rootPart.Position).Unit)
local currentDistance = (rootPart.Position - localRootPart.Position).Magnitude
if currentDistance > Configuration.expectOptionValue("DistanceLimit") then
continue
end
if
playerFromCharacter
and isAlly(playerFromCharacter)
and Configuration.expectToggleValue("IgnoreAllies")
then
continue
end
local mousePosition = userInputService:GetMouseLocation()
local unitRay = workspace.CurrentCamera:ScreenPointToRay(mousePosition.X, mousePosition.Y)
local distanceToCrosshair = unitRay:Distance(rootPart.Position)
targets[#targets + 1] =
Target.new(entity, humanoid, rootPart, distanceToCrosshair, fieldOfViewToEntity, currentDistance)
end
return targets
end)
---Get the best targets through sorting.
---@return Target[]
Targeting.best = LPH_NO_VIRTUALIZE(function()
local sortType = Configuration.expectOptionValue("PlayerSelectionType")
local maxTargets = Configuration.expectOptionValue("MaxTargets")
local localCharacter = players.LocalPlayer and players.LocalPlayer.Character
local localRootPart = localCharacter and localCharacter:FindFirstChild("HumanoidRootPart")
local currentCamera = workspace.CurrentCamera
if localRootPart and currentCamera and not shouldRebuildTargets(localRootPart, currentCamera, sortType, maxTargets) then
return targetCache.targets
end
local targets = Targeting.viable()
local sortFunction = nil
if sortType == "Closest To Crosshair" then
sortFunction = function(first, second)
return first.dc < second.dc
end
end
if sortType == "Closest In Distance" then
sortFunction = function(first, second)
return first.du < second.du
end
end
if sortType == "Least Health" then
sortFunction = function(first, second)
return first.humanoid.Health < second.humanoid.Health
end
end
table.sort(targets, sortFunction)
local sliced = Table.slice(targets, 1, maxTargets)
targetCache.targets = sliced
targetCache.lastAt = os.clock()
targetCache.lastMouse = userInputService:GetMouseLocation()
targetCache.lastLocalPos = localRootPart and localRootPart.Position or nil
targetCache.lastCamCFrame = currentCamera and currentCamera.CFrame or nil
targetCache.sortType = sortType
targetCache.maxTargets = maxTargets
return sliced
end)
---Find our model from a list of best targets.
---@param model Model
---@return Target?
Targeting.find = LPH_NO_VIRTUALIZE(function(model)
for _, target in next, Targeting.best() do
if target.character ~= model then
continue
end
return target
end
end)
-- Return Targeting module.
return Targeting
end)
-- MODULE: Game/InputClient
defineModule("Game/InputClient", function()
local InputClient = {}
local players = game:GetService('Players')
local workspaceService = game:GetService('Workspace')
local runService = game:GetService('RunService')
local virtualInput = game:GetService('VirtualInputManager')
local userInputService = game:GetService('UserInputService')
local Configuration = require('Utility/Configuration')
local Keybinding = require('Game/Keybinding')
local setThreadIdentity = setthreadidentity or set_thread_identity or (syn and syn.set_thread_identity)
local getThreadIdentity = getthreadidentity or get_thread_identity or (syn and syn.get_thread_identity)
local ROBLOX_IDENTITY = 8
local getgc = getgc
local getinfo = debug.getinfo
local hookmetamethod = hookmetamethod
local newcclosure = newcclosure
local clock = os.clock
local DODGE_CAMERA_SPOOF_SECONDS = 0.30
local JUMP_HOLD_SECONDS = 0.05
local M1_MIN_HOLD_SECONDS = 0.02
local BLOCK_TYPE_DEFLECT = 1
local BLOCK_TYPE_NORMAL = 2
local BLOCK_RESEND_COOLDOWN = 0.05
local blockQueue = {}
local blockStack = {}
local blockId = 0
local blockEvent = nil
local fHeldValue = nil
local lastBlockSendAt = 0
local lastUnblockSendAt = 0
local dodgeFunc = nil
local dodgeResolveCooldownUntil = 0
local cameraSpoofActive = false
local cameraSpoofCFrame = nil
local cameraSpoofEpoch = 0
local cameraSpoofUntil = 0
local cameraInstance = nil
local cameraHooked = false
local cameraIndexOld = nil
local cachedLogger = nil
local lastDodgeErrorAt = 0
local blockOff
local function isManualBlockHeld()
local key = Keybinding and Keybinding.info and Keybinding.info.Block
if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then
return false
end
return userInputService:IsKeyDown(key)
end
local function resetDashState()
blockQueue = {}
blockStack = {}
blockId = 0
fHeldValue = nil
blockEvent = nil
lastBlockSendAt = 0
lastUnblockSendAt = 0
dodgeFunc = nil
dodgeResolveCooldownUntil = 0
cameraSpoofActive = false
cameraSpoofCFrame = nil
cameraSpoofEpoch = 0
cameraSpoofUntil = 0
cameraInstance = nil
if blockOff then
task.defer(blockOff)
end
end
do
local lp = players.LocalPlayer
if not lp then
players:GetPropertyChangedSignal("LocalPlayer"):Wait()
lp = players.LocalPlayer
end
if lp then
lp.CharacterAdded:Connect(function()
resetDashState()
end)
lp.CharacterRemoving:Connect(function()
resetDashState()
end)
end
end
local function getLogger()
if cachedLogger then
return cachedLogger
end
local ok, logger = pcall(require, 'Utility/Logger')
if ok then
cachedLogger = logger
return cachedLogger
end
return nil
end
local function withRobloxIdentity(action)
local ok, err
if setThreadIdentity and getThreadIdentity then
local prev = getThreadIdentity()
if prev ~= ROBLOX_IDENTITY then
setThreadIdentity(ROBLOX_IDENTITY)
end
ok, err = pcall(action)
if prev ~= ROBLOX_IDENTITY then
setThreadIdentity(prev)
end
else
ok, err = pcall(action)
end
if not ok then
local logger = getLogger()
if logger and logger.warn then
logger.warn("VirtualInputManager failed: %s", tostring(err))
else
warn(string.format("[Cottom hub AB] VirtualInputManager failed: %s", tostring(err)))
end
end
return ok
end
local VIM_SendKeyEvent = virtualInput and virtualInput.SendKeyEvent or nil
local function sendKeyEvent(isDown, keyCode)
if not virtualInput or not VIM_SendKeyEvent then
return false
end
return withRobloxIdentity(function()
VIM_SendKeyEvent(virtualInput, isDown == true, keyCode, false, game)
end) == true
end
local function sendMouseButtonEvent(isDown)
if not virtualInput then
return false
end
local pos = userInputService:GetMouseLocation()
return withRobloxIdentity(function()
virtualInput:SendMouseButtonEvent(pos.X, pos.Y, 0, isDown == true, game, 0)
end) == true
end
local function useControllerM1()
return Configuration.expectToggleValue("UseControllerM1") == true
end
local function sendControllerM1(isDown)
if not useControllerM1() then
return false
end
return sendKeyEvent(isDown == true, Enum.KeyCode.ButtonR2)
end
local function safeGetInfo(...)
if not getinfo then
return nil
end
local ok, info = pcall(getinfo, ...)
if not ok then
return nil
end
return info
end
local function resolveBlockEvent()
if blockEvent and blockEvent.Parent ~= nil and players.LocalPlayer and players.LocalPlayer.Backpack and blockEvent:IsDescendantOf(players.LocalPlayer.Backpack) then
return blockEvent
end
local lp = players.LocalPlayer
if not lp then
return nil
end
local backpack = lp:FindFirstChild("Backpack")
local traits = backpack and backpack:FindFirstChild("ServerTraits")
local input = traits and traits:FindFirstChild("Input")
if input and input:IsA("RemoteEvent") then
blockEvent = input
return input
end
return nil
end
local function resolveToolRange(tool)
if not tool then
return nil
end
local config = tool:FindFirstChild("Config")
local rangeValue = config and config:FindFirstChild("range")
if not rangeValue then
rangeValue = tool:FindFirstChild("range") or tool:FindFirstChild("Range")
end
if rangeValue and rangeValue.Value ~= nil then
return tostring(rangeValue.Value)
end
local attr = tool:GetAttribute("range") or tool:GetAttribute("Range")
if attr ~= nil then
return tostring(attr)
end
return nil
end
local function buildFirePayload(toolName, rangeValue)
if not toolName or toolName == "" then
return nil
end
local cam = workspaceService.CurrentCamera
if not cam then
return nil
end
local mousePos = userInputService:GetMouseLocation()
local ray = cam:ViewportPointToRay(mousePos.X, mousePos.Y)
local dir = ray.Direction
if dir.Magnitude <= 0 then
return nil
end
local origin = ray.Origin
local hitPos = origin + dir * 500
local mouseHit = CFrame.new(hitPos, hitPos + dir)
local humanoid = players.LocalPlayer and players.LocalPlayer.Character and players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
local air = humanoid and humanoid.FloorMaterial == Enum.Material.Air or false
return {
air = air,
mousehit = mouseHit,
camdir = dir.Unit,
neutral = true,
campos = origin,
range = rangeValue or "2",
ToolName = toolName,
}
end
local function resolveFHeld()
local lp = players.LocalPlayer
if not lp then
return nil
end
local char = lp.Character
local flag = char and char:FindFirstChild("FHeld")
if flag and flag:IsA("BoolValue") then
fHeldValue = flag
return flag
end
local live = workspaceService:FindFirstChild("Live")
local liveChar = live and live:FindFirstChild(lp.Name)
flag = liveChar and liveChar:FindFirstChild("FHeld")
if flag and flag:IsA("BoolValue") then
fHeldValue = flag
return flag
end
return nil
end
local function getBlockingState()
local flag = fHeldValue
local char = players.LocalPlayer and players.LocalPlayer.Character or nil
if flag then
if flag.Parent == nil then
flag = nil
elseif char and not flag:IsDescendantOf(char) then
flag = nil
end
end
if not flag then
flag = resolveFHeld()
end
if flag then
return flag.Value == true, true
end
return false, false
end
local function blockOn()
local ev = resolveBlockEvent()
if ev then
ev:FireServer("f")
return true
end
return false
end
blockOff = function()
local ev = resolveBlockEvent()
if ev then
ev:FireServer("foff")
return true
end
return false
end
local function nextBlockId(prefix)
blockId += 1
return string.format("%s_%d", prefix or "Block", blockId)
end
local function queueBlock(kind, duration)
local wasEmpty = next(blockQueue) == nil
local id = nextBlockId(kind == BLOCK_TYPE_DEFLECT and "Deflect" or "Block")
blockQueue[id] = { start = clock(), type = kind, dt = duration }
if wasEmpty then
local blocking = getBlockingState()
if not blocking then
blockOn()
lastBlockSendAt = clock()
end
end
return id
end
local function queueBlockWithId(id, kind, duration)
if not id or id == "" then
return queueBlock(kind, duration)
end
local wasEmpty = next(blockQueue) == nil
blockQueue[id] = { start = clock(), type = kind, dt = duration }
if wasEmpty then
local blocking = getBlockingState()
if not blocking then
blockOn()
lastBlockSendAt = clock()
end
end
return id
end
local function unqueueBlock(id)
if not id or not blockQueue[id] then
return
end
if timing._id == "rbxassetid://13631792671" then
local dummyAction = { ihbc = true }
if not self:valid(timing, dummyAction) then
return
end
local dist = distance or self:distance(self.entity) or math.huge
local holdSeconds = dist <= 40 and 1.2 or 1.6
InputClient.blockHoldSeconds(holdSeconds)
return
end
blockQueue[id] = nil
end
local function stopBlockId(id)
if not id or not blockQueue[id] then
return
end
blockQueue[id] = nil
end
local function blockAcquire()
local id = queueBlock(BLOCK_TYPE_NORMAL, nil)
blockStack[#blockStack + 1] = id
end
local function blockRelease()
local idx = #blockStack
if idx <= 0 then
return
end
local id = blockStack[idx]
blockStack[idx] = nil
unqueueBlock(id)
end
local function cleanupBlockQueue(blocking)
local now = clock()
for id, data in next, blockQueue do
if data.dt and now - data.start >= data.dt then
blockQueue[id] = nil
elseif data.type == BLOCK_TYPE_DEFLECT and blocking then
blockQueue[id] = nil
end
end
end
local blockSyncConn = nil
local lastWantsBlock = false
local function ensureBlockSync()
if blockSyncConn then
return
end
blockSyncConn = runService.RenderStepped:Connect(LPH_NO_VIRTUALIZE(function()
local blocking, hasFlag = getBlockingState()
cleanupBlockQueue(blocking)
local wantsBlock = next(blockQueue) ~= nil
if wantsBlock then
if not blocking then
local now = clock()
if now - lastBlockSendAt >= BLOCK_RESEND_COOLDOWN then
blockOn()
lastBlockSendAt = now
end
end
lastWantsBlock = true
return
end
local autoDefenseEnabled = Configuration.expectToggleValue("EnableAutoDefense")
local m1TradeEnabled = Configuration.expectToggleValue("EnableM1AutoTrade")
if not autoDefenseEnabled and not m1TradeEnabled then
lastWantsBlock = false
return
end
if m1TradeEnabled and not autoDefenseEnabled then
if isManualBlockHeld() then
lastWantsBlock = false
return
end
if not lastWantsBlock then
return
end
end
if blocking or not hasFlag then
local now = clock()
if now - lastUnblockSendAt >= BLOCK_RESEND_COOLDOWN then
blockOff()
lastUnblockSendAt = now
end
end
lastWantsBlock = false
end))
end
ensureBlockSync()
function InputClient.block(state)
if state == true then
blockAcquire()
else
blockRelease()
end
end
function InputClient.deflect()
local holdSeconds = Configuration.expectOptionValue("BlockHoldSeconds")
if holdSeconds == nil then
holdSeconds = (Configuration.expectOptionValue("DeflectHoldTime") or 0) / 1000
end
return InputClient.blockHoldSeconds(math.max(holdSeconds or 0, 0))
end
function InputClient.blockHoldSeconds(seconds)
local duration = seconds or 0
if duration <= 0 then
return false
end
queueBlock(BLOCK_TYPE_NORMAL, duration)
return true
end
function InputClient.fireTool(toolName, rangeValue)
local ev = resolveBlockEvent()
if not ev then
return false
end
local payload = buildFirePayload(toolName, rangeValue)
if not payload then
return false
end
local ok = pcall(ev.FireServer, ev, "fire", payload)
return ok == true
end
function InputClient.fireToolObject(tool)
if not tool then
return false
end
return InputClient.fireTool(tool.Name, resolveToolRange(tool))
end
function InputClient.blockTap(seconds)
local duration = seconds or 0
if duration <= 0 then
return false
end
blockOn()
task.delay(0.02, blockOn)
task.delay(duration, function()
blockOff()
end)
return true
end
function InputClient.blockTag(id, state, duration, deflect)
if not id or id == "" then
return InputClient.block(state)
end
if state then
local kind = deflect and BLOCK_TYPE_DEFLECT or BLOCK_TYPE_NORMAL
queueBlockWithId(id, kind, duration)
return true
end
stopBlockId(id)
return true
end
local function normalizeKeyCode(key)
if typeof(key) == "EnumItem" and key.EnumType == Enum.KeyCode then
return key
end
return nil
end
function InputClient.keyHold(keyCode, seconds)
local duration = seconds or 0
if duration <= 0 then
return false
end
local key = normalizeKeyCode(keyCode)
if not key then
return false
end
if not sendKeyEvent(true, key) then
return false
end
task.delay(duration, function()
sendKeyEvent(false, key)
end)
return true
end
local function resolveJumpKey()
local key = Keybinding and Keybinding.info and Keybinding.info.Jump or Enum.KeyCode.Space
if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then
key = Enum.KeyCode.Space
end
return key
end
function InputClient.jumpHold(seconds)
local duration = seconds or JUMP_HOLD_SECONDS
return InputClient.keyHold(resolveJumpKey(), duration)
end
local function resolveSprintKey()
local key = Keybinding and Keybinding.info and (Keybinding.info.Sprint or Keybinding.info.Run)
if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then
key = Enum.KeyCode.LeftShift
end
return key
end
function InputClient.sprintHold(seconds)
local duration = seconds or 0
if duration <= 0 then
return false
end
return InputClient.keyHold(resolveSprintKey(), duration)
end
function InputClient.jump()
local key = Keybinding and Keybinding.info and Keybinding.info.Jump or Enum.KeyCode.Space
if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then
key = Enum.KeyCode.Space
end
if not sendKeyEvent(true, key) then
return false
end
task.delay(JUMP_HOLD_SECONDS, function()
sendKeyEvent(false, key)
end)
return true
end
function InputClient.tapKey(keyCode, holdSeconds)
if typeof(keyCode) ~= "EnumItem" then
return false
end
local duration = holdSeconds or 0.02
if not sendKeyEvent(true, keyCode) then
return false
end
task.delay(duration, function()
sendKeyEvent(false, keyCode)
end)
return true
end
function InputClient.m1Hold(seconds)
local duration = seconds or 0
if duration <= 0 then
return false
end
InputClient._m1AutoUntil = (InputClient._m1AutoUntil or 0)
InputClient._m1AutoUntil = math.max(InputClient._m1AutoUntil, clock() + duration + 0.06)
if useControllerM1() then
if InputClient._m1KeyDown then
sendControllerM1(false)
InputClient._m1KeyDown = false
end
else
if InputClient._m1Down then
sendMouseButtonEvent(false)
InputClient._m1Down = false
end
end
InputClient._m1Token = (InputClient._m1Token or 0) + 1
local token = InputClient._m1Token
if useControllerM1() then
if not sendControllerM1(true) then
return false
end
InputClient._m1KeyDown = true
else
if not sendMouseButtonEvent(true) then
return false
end
InputClient._m1Down = true
end
local function releaseIfStill(token)
if InputClient._m1Token ~= token then
return
end
if useControllerM1() then
sendControllerM1(false)
InputClient._m1KeyDown = false
else
sendMouseButtonEvent(false)
InputClient._m1Down = false
end
end
task.delay(duration, function()
releaseIfStill(token)
end)
task.delay(duration + 0.03, function()
releaseIfStill(token)
end)
task.delay(duration + 0.06, function()
releaseIfStill(token)
end)
return true
end
function InputClient.m1Release()
InputClient._m1Token = (InputClient._m1Token or 0) + 1
if useControllerM1() then
if InputClient._m1KeyDown then
sendControllerM1(false)
InputClient._m1KeyDown = false
end
else
if InputClient._m1Down then
sendMouseButtonEvent(false)
InputClient._m1Down = false
end
end
InputClient._m1AutoUntil = 0
return true
end
function InputClient.m1Tap(seconds)
local duration = seconds or M1_MIN_HOLD_SECONDS
return InputClient.m1Hold(duration)
end
local m1ReleaseConn = nil
local function ensureM1Release()
if m1ReleaseConn then
return
end
m1ReleaseConn = runService.RenderStepped:Connect(LPH_NO_VIRTUALIZE(function()
if useControllerM1() then
if InputClient._m1KeyDown and InputClient._m1AutoUntil and clock() > InputClient._m1AutoUntil then
InputClient._m1ReleaseTries = (InputClient._m1ReleaseTries or 0) + 1
sendControllerM1(false)
if InputClient._m1ReleaseTries >= 6 then
InputClient._m1KeyDown = false
InputClient._m1AutoUntil = 0
InputClient._m1ReleaseTries = 0
end
else
InputClient._m1ReleaseTries = 0
end
return
end
if InputClient._m1Down and InputClient._m1AutoUntil and clock() > InputClient._m1AutoUntil then
-- Retry release for a few frames if VIM misses the up event.
InputClient._m1ReleaseTries = (InputClient._m1ReleaseTries or 0) + 1
sendMouseButtonEvent(false)
if InputClient._m1ReleaseTries >= 6 then
InputClient._m1Down = false
InputClient._m1AutoUntil = 0
InputClient._m1ReleaseTries = 0
end
else
InputClient._m1ReleaseTries = 0
end
end))
end
ensureM1Release()
local function normalizeDirection(direction)
if not direction or direction == "" then
direction = Configuration.expectOptionValue("DefaultDashDirection") or "S"
end
if direction == "Random" then
local options = { "W", "A", "S", "D" }
direction = options[math.random(1, #options)]
end
if typeof(direction) == "EnumItem" then
direction = tostring(direction.Name)
end
direction = tostring(direction)
if direction == "W" or direction == "w" then
direction = "forward"
elseif direction == "A" or direction == "a" then
direction = "left"
elseif direction == "D" or direction == "d" then
direction = "right"
elseif direction == "S" or direction == "s" then
direction = "back"
else
direction = direction:lower()
end
if direction == "forward" or direction == "back" or direction == "left" or direction == "right" then
return direction
end
return "back"
end
local function resolveDodgeFunction()
if dodgeFunc then
return dodgeFunc
end
if dodgeResolveCooldownUntil > 0 and clock() < dodgeResolveCooldownUntil then
return nil
end
local gcList = getgc(true)
for i = 1, #gcList do
local fn = gcList[i]
if type(fn) == "function" then
local info = getinfo(fn, "nS")
if info and info.name == "dodge" and info.source and info.source:find("LocalCharacterScript", 1, true) then
dodgeFunc = fn
break
end
end
end
if not dodgeFunc then
dodgeResolveCooldownUntil = clock() + 1
end
return dodgeFunc
end
local function getDashLookVector(direction, hrpCFrame)
if not hrpCFrame then
return nil
end
local d = direction or "forward"
if d == "back" then
return -hrpCFrame.LookVector
elseif d == "left" then
return -hrpCFrame.RightVector
elseif d == "right" then
return hrpCFrame.RightVector
end
return hrpCFrame.LookVector
end
local function computeSpoofCameraCFrame(direction)
local char = players.LocalPlayer and players.LocalPlayer.Character or nil
if not char then
return nil
end
local hrp = char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")
if not hrp then
return nil
end
local lookVec = getDashLookVector(direction, hrp.CFrame)
if not lookVec then
return nil
end
local pos = hrp.Position
return CFrame.new(pos, pos + lookVec)
end
local function ensureCameraHook()
if cameraHooked then
return
end
local function isDodgeOnStack()
if not dodgeFunc then
return false
end
for level = 2, 7 do
local info = safeGetInfo(level, 'f')
if info and info.func == dodgeFunc then
return true
end
end
return false
end
local handler = LPH_NO_VIRTUALIZE(function(self, key)
if cameraSpoofActive and cameraInstance and self == cameraInstance then
if cameraSpoofUntil > 0 and clock() > cameraSpoofUntil then
cameraSpoofActive = false
cameraSpoofCFrame = nil
cameraSpoofUntil = 0
return cameraIndexOld(self, key)
end
if not isDodgeOnStack() then
return cameraIndexOld(self, key)
end
if key == 'CFrame' or key == 'CoordinateFrame' or key == 'Focus' then
return cameraSpoofCFrame
end
end
return cameraIndexOld(self, key)
end)
local wrapper = handler
if type(newcclosure) == 'function' then
wrapper = newcclosure(handler)
end
local old = hookmetamethod(game, '__index', wrapper)
if type(old) ~= 'function' then
return
end
cameraIndexOld = old
cameraHooked = true
end
local function normalizeDashDirection(direction)
if direction == "left" then
return "right"
end
return direction
end
local function callDodge()
local fn = resolveDodgeFunction()
if not fn then
return false
end
local ok, err = pcall(fn, "dodge")
if not ok then
dodgeFunc = nil
dodgeResolveCooldownUntil = clock() + 0.5
local now = clock()
if now - lastDodgeErrorAt > 2 then
lastDodgeErrorAt = now
local logger = getLogger()
if logger and logger.warn then
logger.warn("Dash failed: %s", tostring(err))
else
warn(string.format("[Cottom hub AB] Dash failed: %s", tostring(err)))
end
end
return false
end
return true
end
local function dodgeDash(direction)
if not resolveDodgeFunction() then
return false
end
direction = normalizeDashDirection(direction)
local spoof = computeSpoofCameraCFrame(direction)
local epoch = cameraSpoofEpoch + 1
if spoof ~= nil then
cameraInstance = workspaceService.CurrentCamera
cameraSpoofEpoch = epoch
cameraSpoofCFrame = spoof
cameraSpoofActive = true
cameraSpoofUntil = clock() + DODGE_CAMERA_SPOOF_SECONDS
ensureCameraHook()
else
cameraSpoofActive = false
cameraSpoofCFrame = nil
cameraSpoofUntil = 0
cameraInstance = nil
end
local ok = callDodge()
if spoof ~= nil then
local restore = function()
if cameraSpoofEpoch ~= epoch then
return
end
cameraSpoofActive = false
cameraSpoofCFrame = nil
cameraSpoofUntil = 0
end
if DODGE_CAMERA_SPOOF_SECONDS > 0 then
task.delay(DODGE_CAMERA_SPOOF_SECONDS, restore)
else
restore()
end
end
return ok
end
function InputClient.dash(direction)
return dodgeDash(normalizeDirection(direction))
end
return InputClient
end)
-- MODULE: Features/Combat/UsingSkillWatcher
defineModule("Features/Combat/UsingSkillWatcher", function()
local UsingSkillWatcher = { rules = {} }
local Players = game:GetService("Players")
local workspaceService = game:GetService("Workspace")
local userInputService = game:GetService("UserInputService")
local textChatService = game:GetService("TextChatService")
local InputClient = require("Game/InputClient")
local Configuration = require("Utility/Configuration")
local Library = require("GUI/Library")
local AttributeListener = require("Features/Combat/AttributeListener")
local Defender = require("Features/Combat/Objects/Defender")
local Logger = require("Utility/Logger")
local LocalPlayer = Players.LocalPlayer
if not LocalPlayer then
Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
LocalPlayer = Players.LocalPlayer
end
local lower = string.lower
local hitboxDefender = Defender.new()
local clock = os.clock
local pollActive = false
local pollTask = nil
local pollInterval = NO_HITBOX_SKILL_POLL_SECONDS
local NO_HITBOX_COOLDOWN = 0.03
local MAX_NO_HITBOX_DELAY = 0.06
local skillContext = {}
local SKILL_LOCK_BASE = 0.08
local runSkillAction
local skillDelaySeconds
local fireNoHitbox
local function noHitboxReactionBiasSeconds()
return NO_HITBOX_REACTION_BIAS_SECONDS
end
local function noHitboxSkillPollSeconds()
return NO_HITBOX_SKILL_POLL_SECONDS
end
local function normalizeShape(shape)
return shape == "Ball" and "Ball" or "Box"
end
local function passesDistance(def, rule)
if rule.imdd == nil and rule.imxd == nil then
return true
end
local distance = hitboxDefender:distance(def.character)
if not distance then
return false
end
local minDistance = rule.imdd or 0
local maxDistance = rule.imxd or math.huge
return distance >= minDistance and distance <= maxDistance
end
local function passesHitbox(def, rule)
local _ = def
local _rule = rule
return true
end
local function passesHitboxRaw(def, rule)
local _ = def
local _rule = rule
return true
end
local function blockAcquire()
return InputClient.block(true)
end
local function blockRelease()
return InputClient.block(false)
end
local function blockHoldSeconds(seconds)
return InputClient.blockHoldSeconds(seconds)
end
local function jumpNow()
return InputClient.jump()
end
local function allowActions(tag)
if not Configuration.expectToggleValue("EnableAutoDefense") then
return false
end
local character = Players.LocalPlayer and Players.LocalPlayer.Character
if not character then
return false
end
local selectedFilters = Configuration.expectOptionValue("AutoDefenseFilters") or {}
local function filterEnabled(name, legacy)
return selectedFilters[name] or (legacy and selectedFilters[legacy])
end
local currentState = character:GetAttribute("CurrentState")
if filterEnabled("Disable When Knocked Recently") and AttributeListener.krecently and AttributeListener.krecently() then
return false
end
if filterEnabled("Disable When In Dash") and currentState == "Dashing" then
return false
end
if filterEnabled("Disable When In Flashstep") and currentState == "Flashstep" then
return false
end
if currentState == "Attacking" or currentState == "Skill" then
return false
end
local chatInputBarConfiguration = textChatService:FindFirstChildOfClass("ChatInputBarConfiguration")
if filterEnabled("Disable When Textbox Focused")
and (userInputService:GetFocusedTextBox() or (chatInputBarConfiguration and chatInputBarConfiguration.IsFocused))
then
return false
end
if filterEnabled("Disable When Window Not Active") and iswindowactive and not iswindowactive() then
return false
end
if filterEnabled("Disable When Holding Block")
and userInputService:IsKeyDown(Enum.KeyCode.F)
then
return false
end
if tag == "Skill" and filterEnabled("Filter Out Skills", "Filter Out Mantras") then
return false
end
return true
end
local function normalizeSkillValue(raw)
local lowerValue = lower(raw)
if lowerValue:find("%s") then
return lowerValue, lowerValue:gsub("%s+", "")
end
return lowerValue, lowerValue
end
local function canonicalSkillValue(raw)
return tostring(raw or ""):gsub("[^%w]", "")
end
local function isWordBoundaryChar(ch)
return ch == nil or ch == "" or not string.match(ch, "[%w]")
end
local function boundedContains(haystack, needle)
if type(haystack) ~= "string" or type(needle) ~= "string" or needle == "" then
return false
end
local from = 1
while true do
local s, e = haystack:find(needle, from, true)
if not s then
return false
end
local prev = (s > 1) and haystack:sub(s - 1, s - 1) or nil
local nextc = (e < #haystack) and haystack:sub(e + 1, e + 1) or nil
if isWordBoundaryChar(prev) and isWordBoundaryChar(nextc) then
return true
end
from = s + 1
end
end
local function getSkillLogIdentity(def)
local plr = def.player
if not plr and def.character then
plr = Players:GetPlayerFromCharacter(def.character)
end
local name = (plr and plr.Name) or (def.character and def.character.Name) or "Unknown"
return name, plr == LocalPlayer
end
local function updateSkillEntryLabel(entry, tag, who, state, value, seconds, secondsLabel)
if not entry or not entry.Label then
return
end
local displayValue = value
if displayValue == "" then
displayValue = "(cleared)"
end
local timeText = os.date("%H:%M:%S")
local suffix = string.format(" | at=%s", timeText)
if type(seconds) == "number" then
local label = secondsLabel or "elapsed"
suffix = suffix .. string.format(" | %s=%.3fs", label, seconds)
end
entry.Label.Text = string.format("(%s) %s UsingSkill %s: %s%s", tag, who, state, displayValue, suffix)
end
local function matchSkill(lowerValue, compactValue, token, spacedToken)
local tokenLower = lower(tostring(token or ""))
if tokenLower == "" then
return false
end
local spacedLower = spacedToken and lower(tostring(spacedToken)) or nil
if spacedLower and (lowerValue == spacedLower or boundedContains(lowerValue, spacedLower)) then
return true
end
if lowerValue == tokenLower or boundedContains(lowerValue, tokenLower) then
return true
end
local canonicalValue = canonicalSkillValue(lowerValue)
local canonicalCompact = canonicalSkillValue(compactValue)
local canonicalToken = canonicalSkillValue(tokenLower)
if canonicalToken ~= "" and (canonicalValue == canonicalToken or canonicalCompact == canonicalToken) then
return true
end
if spacedLower then
local canonicalSpaced = canonicalSkillValue(spacedLower)
if canonicalSpaced ~= "" and (canonicalValue == canonicalSpaced or canonicalCompact == canonicalSpaced) then
return true
end
end
return false
end
local function jumpTwice()
InputClient.jump()
task.delay(0.08, function()
InputClient.jump()
end)
end
local function blockFor(seconds)
local hold = seconds or 0
if hold <= 0 and NO_HITBOX_ONLY then
hold = NO_HITBOX_MIN_BLOCK_SECONDS
end
if hold > 0 then
InputClient.blockHoldSeconds(hold)
end
end
local function dash(dir)
InputClient.dash(dir)
end
local function dashAfter(seconds, dir, withJump)
local delaySeconds = seconds or 0
if delaySeconds <= 0 then
if withJump then
jumpTwice()
end
return dash(dir)
end
task.delay(delaySeconds, function()
if withJump then
jumpTwice()
end
dash(dir)
end)
end
local function blockAfter(seconds, duration)
local delaySeconds = seconds or 0
if delaySeconds <= 0 then
return blockFor(duration)
end
task.delay(delaySeconds, function()
blockFor(duration)
end)
end
local localSkillEndRules = {}
local function addSkillRule(token, spacedToken, onEnd)
localSkillEndRules[#localSkillEndRules + 1] = {
token = token,
spacedToken = spacedToken,
onEnd = onEnd,
}
end
local function addAssetRule(id, onEnd)
local token = "rbxassetid://" .. tostring(id)
addSkillRule(token, nil, onEnd)
addSkillRule(tostring(id), nil, onEnd)
end
addSkillRule("uzumakibarrage", "uzumaki barrage", function()
blockFor(0.3)
end)
addSkillRule("shadowclone", "shadow clone", function()
blockFor(0.8)
end)
addSkillRule("clonethrow", "clone throw", function()
blockFor(1.2)
end)
addSkillRule("fireballjutsu", "fireball jutsu", function(_, _, _, duration)
if duration and duration >= 2.0 then
blockFor(3)
else
dashAfter(0.65, "right")
end
end)
addSkillRule("chidori", nil, function()
dash("back")
end)
addSkillRule("fumashuriken", "fuma shuriken", function()
blockFor(1.2)
end)
addSkillRule("lionsbarrage", "lion's barrage", function()
blockFor(0.8)
end)
addSkillRule("headhunterjutsu", "headhunter jutsu", function()
blockFor(0.8)
end)
addSkillRule("kunai", nil, function()
blockFor(1.1)
end)
addSkillRule("primarylotus", "primary lotus", function()
blockFor(0.6)
end)
addSkillRule("shuriken", nil, function()
blockFor(0.5)
end)
addSkillRule("leafhurricane", "leaf hurricane", function()
blockFor(0.8)
end)
addSkillRule("risingwind", "rising wind", function()
dash("back")
end)
addSkillRule("rotation", nil, function()
dash("back")
end)
addSkillRule("explosivekunai", "explosive kunai", function()
blockFor(0.8)
end)
addSkillRule("64palms", "64 palms", function()
blockFor(6)
end)
addSkillRule("airpalm", "air palm", function()
blockFor(0.7)
end)
addSkillRule("waterclone", "water clone", function()
blockFor(1.2)
end)
addSkillRule("decapitate", nil, function()
task.delay(0.6, function()
jumpTwice()
dash("back")
end)
end)
addSkillRule("swordthrow", "sword throw", function()
blockFor(0.5)
end)
addSkillRule("sandcoffin", "sand coffin", function()
blockFor(0.3)
end)
addSkillRule("sandtsunami", "sand tsunami", function()
jumpTwice()
dash("right")
end)
addSkillRule("sandspikes", "sand spikes", function()
blockFor(1.2)
end)
addSkillRule("chidoristream", "chidori stream", function()
blockFor(1.1)
end)
addSkillRule("chidorisenbon", "chidori senbon", function()
blockFor(0.75)
end)
addSkillRule("chidoritruespear", "chidori true spear", function()
jumpTwice()
dash("right")
end)
addSkillRule("rasengancombo", "rasengan combo", function()
blockFor(0.3)
end)
addSkillRule("shadowclones", "shadow clones", function()
blockFor(0.9)
end)
addSkillRule("rasenganthrow", "rasengan throw", function()
task.delay(0.7, function()
jumpTwice()
dash("right")
end)
end)
addSkillRule("shurikenassualt", "shuriken assualt", function()
blockFor(0.8)
end)
addSkillRule("demonicillusion", "demonic illusion", function()
blockFor(0.65)
end)
addSkillRule("asurapath", "asura path", function()
blockAfter(0.3, 0.75)
end)
addSkillRule("sixpathscombo", "six paths combo", function()
blockFor(1.1)
end)
addSkillRule("almightypush", "almighty push", function(_, _, _, duration)
if duration and duration > 1.5 then
dash("back")
else
blockFor(1.2)
end
end)
addSkillRule("universalpull", "universal pull", function()
blockFor(0.8)
end)
addSkillRule("nunchaku", nil, function()
blockFor(6)
end)
addSkillRule("severehurricane", "severe hurricane", function()
task.delay(0.19, function()
jumpTwice()
dash("right")
end)
end)
addSkillRule("dynamicentry", "dynamic entry", function()
blockFor(1.5)
end)
addSkillRule("flipkick", "flip kick", function()
blockAfter(0.4, 0.9)
end)
addSkillRule("vicegrip", "vice grip", function()
blockFor(1.1)
end)
addSkillRule("ligerbomb", "liger bomb", function()
blockFor(0.4)
end)
addSkillRule("sharkbombjutsu", "shark bomb jutsu", function()
task.delay(0.5, function()
dash("right")
end)
end)
addSkillRule("waterprison", "water prison", function()
blockAfter(0.7, 0.4)
end)
addSkillRule("samehadafeast", "samehada feast", function()
jumpTwice()
dash("back")
end)
addAssetRule(3240243801, function()
blockFor(0.3)
end)
addAssetRule(3240242879, function()
blockFor(0.3)
end)
addAssetRule(3240241715, function()
blockFor(0.3)
end)
addAssetRule(3240240417, function()
blockFor(0.3)
end)
addAssetRule(3240245051, function()
dash("back")
end)
addAssetRule(3263871265, function()
blockFor(0.3)
end)
addAssetRule(3263870104, function()
blockFor(0.3)
end)
addAssetRule(3263925841, function()
dash("back")
end)
addAssetRule(12831969898, function()
dashAfter(0.2, "right")
end)
addAssetRule(14437150122, function()
blockFor(0.3)
end)
addAssetRule(14437148019, function()
blockFor(0.3)
end)
addAssetRule(14437145085, function()
blockFor(0.3)
end)
addAssetRule(14436312737, function()
blockFor(0.3)
end)
addAssetRule(14437152064, function()
dash("back")
end)
local function handleLocalSkillEnd(rawValue, durationSeconds)
if type(rawValue) ~= "string" or rawValue == "" then
return
end
local lowerValue, compactValue = normalizeSkillValue(rawValue)
local bestRule = nil
local bestLen = -1
for _, rule in ipairs(localSkillEndRules) do
if rule and rule.token and matchSkill(lowerValue, compactValue, rule.token, rule.spacedToken) then
local len = #tostring(rule.token)
if rule.spacedToken and #rule.spacedToken > len then
len = #rule.spacedToken
end
if len > bestLen then
bestLen = len
bestRule = rule
end
end
end
if bestRule and type(bestRule.onEnd) == "function" then
bestRule.onEnd(nil, lowerValue, compactValue, durationSeconds)
end
end
local function deferHitboxAction(def, key, rule, delaySeconds, callback)
local delay = delaySeconds or 0
local info = def.stateInfo and def.stateInfo[key]
if not info then
return false
end
info.delayToken = (info.delayToken or 0) + 1
local token = info.delayToken
task.delay(delay, function()
if not def.states or def.states[key] ~= true then
return
end
if info.delayToken ~= token then
return
end
if not allowActions("Skill") then
return
end
if not passesDistance(def, rule) then
return
end
if type(callback) == "function" then
local ok, err = pcall(function()
executeDefensiveAction(def, rule, callback)
end)
if not ok then
Logger.warn("UsingSkill deferred callback failed: %s", tostring(err))
end
end
end)
return true
end
local function logUsingSkill(def, rawValue, isLocal)
local toggleKey = isLocal and "LogUsingSkillLocal" or "LogUsingSkillOthers"
if not Configuration.expectToggleValue(toggleKey) then
def.lastLoggedValue = nil
def.lastSkillValue = nil
def.lastSkillStart = nil
def.lastSkillEntry = nil
def.lastSkillUpdateAt = nil
def.lastSkillToken = (def.lastSkillToken or 0) + 1
return
end
local value = type(rawValue) == "string" and rawValue or ""
local now = clock()
local playerName = (def.player and def.player.Name) or (def.character and def.character.Name) or "Unknown"
local tag = isLocal and "Local" or "Other"
if def.lastLoggedValue == value then
if value ~= "" and def.lastSkillEntry and def.lastSkillStart then
if not def.lastSkillUpdateAt or (now - def.lastSkillUpdateAt) >= 0.1 then
def.lastSkillUpdateAt = now
local elapsed = now - def.lastSkillStart
updateSkillEntryLabel(def.lastSkillEntry, tag, playerName, "Running", value, elapsed, "elapsed")
end
end
return
end
local previousValue = def.lastSkillValue or ""
if value ~= previousValue then
if previousValue ~= "" then
local duration = def.lastSkillStart and (now - def.lastSkillStart) or nil
if def.lastSkillEntry then
updateSkillEntryLabel(def.lastSkillEntry, tag, playerName, "End", previousValue, duration, "lasted")
end
if Library and Library.AddUsingSkillEntry then
Library:AddUsingSkillEntry(playerName, previousValue, isLocal, "End", duration)
end
if isLocal then
handleLocalSkillEnd(previousValue, duration)
end
def.lastSkillEntry = nil
def.lastSkillUpdateAt = nil
def.lastSkillToken = (def.lastSkillToken or 0) + 1
end
if value ~= "" then
def.lastSkillValue = value
def.lastSkillStart = now
if Library and Library.AddUsingSkillEntry then
def.lastSkillToken = (def.lastSkillToken or 0) + 1
local token = def.lastSkillToken
Library:AddUsingSkillEntry(playerName, value, isLocal, "Start", nil, function(entry)
if def.lastSkillToken ~= token or def.lastSkillValue ~= value then
return
end
def.lastSkillEntry = entry
def.lastSkillUpdateAt = now
end)
end
else
def.lastSkillValue = ""
def.lastSkillStart = nil
def.lastSkillEntry = nil
def.lastSkillUpdateAt = nil
def.lastSkillToken = (def.lastSkillToken or 0) + 1
end
end
def.lastLoggedValue = value
end
local usingSkillDefs = {}
function UsingSkillWatcher.setRules(rules)
UsingSkillWatcher.rules = rules or {}
for _, rule in pairs(UsingSkillWatcher.rules) do
if typeof(rule.hitbox) == "table" and typeof(rule.hitbox.X) == "number" then
rule.hitbox = Vector3.new(rule.hitbox.X, rule.hitbox.Y, rule.hitbox.Z)
end
if typeof(rule.offset) == "table" and typeof(rule.offset.X) == "number" then
rule.offset = Vector3.new(rule.offset.X, rule.offset.Y, rule.offset.Z)
end
if rule.shape then
rule.shape = normalizeShape(rule.shape)
end
if rule.fhb == nil then
rule.fhb = true
end
end
end
function UsingSkillWatcher.blockAcquire()
blockAcquire()
end
function UsingSkillWatcher.blockRelease()
blockRelease()
end
function UsingSkillWatcher.blockHoldSeconds(seconds)
return blockHoldSeconds(seconds)
end
function UsingSkillWatcher.jump()
return jumpNow()
end
function UsingSkillWatcher.deferHitbox(def, key, delaySeconds, callback)
local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key]
if not rule or not def then
return false
end
return deferHitboxAction(def, key, rule, delaySeconds, callback)
end
local function updateOwner(def)
local obj = def.obj
local character = obj and obj:FindFirstAncestorWhichIsA("Model")
def.character = character
def.player = character and Players:GetPlayerFromCharacter(character) or nil
end
local function safeRuleStart(def, rule, lowerValue, compactValue)
local ok, err = pcall(function()
executeDefensiveAction(def, rule, function()
return rule.onStart(def, lowerValue, compactValue)
end)
end)
if not ok then
Logger.warn("UsingSkill onStart failed: %s", tostring(err))
end
end
local function executeRuleOnStart(def, key, rule, lowerValue, compactValue)
if not def or not rule or type(rule.onStart) ~= "function" then
return
end
if rule.ihbc then
if fireNoHitbox then
fireNoHitbox(def, key, rule, lowerValue, compactValue)
else
safeRuleStart(def, rule, lowerValue, compactValue)
end
return
end
if runSkillAction then
runSkillAction(def, key, rule, lowerValue, compactValue, function()
safeRuleStart(def, rule, lowerValue, compactValue)
end)
else
safeRuleStart(def, rule, lowerValue, compactValue)
end
end
function UsingSkillWatcher.update(def)
if not Configuration.expectToggleValue("EnableAutoDefense") then
return
end
updateOwner(def)
local plr = def.player
local skill = def.obj
local rawValue = skill and skill.Value
local now = clock()
if plr == LocalPlayer then
logUsingSkill(def, rawValue, true)
if def and def.states then
for key, active in pairs(def.states) do
if active then
def.states[key] = false
local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key]
if rule and type(rule.onStop) == "function" then
rule.onStop(def)
end
end
end
end
return
end
if plr then
logUsingSkill(def, rawValue, false)
end
if not plr then
if def and def.states then
for key, active in pairs(def.states) do
if active then
def.states[key] = false
local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key]
if rule and type(rule.onStop) == "function" then
rule.onStop(def)
end
end
end
end
return
end
if not skill or skill.Parent == nil then
if def and def.states then
for key, active in pairs(def.states) do
if active then
def.states[key] = false
local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key]
if rule and type(rule.onStop) == "function" then
rule.onStop(def)
end
end
end
end
return
end
local value = skill.Value
if type(value) ~= "string" then
if def and def.states then
for key, active in pairs(def.states) do
if active then
def.states[key] = false
local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key]
if rule and type(rule.onStop) == "function" then
rule.onStop(def)
end
end
end
end
return
end
local lowerValue, compactValue = normalizeSkillValue(value)
def.lastValue = lowerValue
local canAct = allowActions("Skill")
def.states = def.states or {}
def.stateInfo = def.stateInfo or {}
for key, rule in pairs(UsingSkillWatcher.rules) do
if not rule or not rule.token then
continue
end
local info = def.stateInfo[key]
if not info then
info = { holdUntil = 0 }
def.stateInfo[key] = info
end
local tokenMatched = canAct and matchSkill(lowerValue, compactValue, rule.token, rule.spacedToken)
if tokenMatched and type(rule.match) == "function" and not rule.match(def, lowerValue, compactValue) then
tokenMatched = false
end
local isDeferred = rule.deferHitboxSeconds and rule.deferHitboxSeconds > 0
local distanceMatched = tokenMatched and passesDistance(def, rule)
local wasActive = def.states[key] == true
if NO_HITBOX_ONLY then
if distanceMatched then
if not wasActive then
def.states[key] = true
info.hitboxActive = true
info.guardPulseAt = now
if handleAutoCounterTiming then
handleAutoCounterTiming({ tag = "Skill" }, "SkillWatcher", hitboxDefender:distance(def.character))
end
blockFor((rule and rule.minBlockSeconds) or NO_HITBOX_MIN_BLOCK_SECONDS)
if type(rule.onStart) == "function" then
executeRuleOnStart(def, key, rule, lowerValue, compactValue)
end
else
if type(rule.onTick) == "function" then
rule.onTick(def, lowerValue, compactValue)
else
local pulseAt = info.guardPulseAt or 0
if now - pulseAt >= NO_HITBOX_ACTIVE_GUARD_PULSE_SECONDS then
info.guardPulseAt = now
blockFor((rule and rule.minBlockSeconds) or NO_HITBOX_MIN_BLOCK_SECONDS)
end
end
end
elseif wasActive then
def.states[key] = false
info.hitboxActive = false
if info.trackToken then
info.trackToken = info.trackToken + 1
end
if type(rule.onStop) == "function" then
rule.onStop(def, lowerValue, compactValue)
end
end
else
local ruleMatched = distanceMatched and passesHitbox(def, rule)
local hitboxWasActive = info.hitboxActive == true
if tokenMatched and not wasActive then
def.states[key] = true
info.hitboxActive = false
if handleAutoCounterTiming then
handleAutoCounterTiming({ tag = "Skill" }, "SkillWatcher", hitboxDefender:distance(def.character))
end
if isDeferred and type(rule.onStart) == "function" then
executeRuleOnStart(def, key, rule, lowerValue, compactValue)
end
elseif not tokenMatched and wasActive then
def.states[key] = false
info.hitboxActive = false
if info.trackToken then
info.trackToken = info.trackToken + 1
end
if type(rule.onStop) == "function" then
rule.onStop(def, lowerValue, compactValue)
end
end
if tokenMatched and not isDeferred then
if ruleMatched and not hitboxWasActive then
info.hitboxActive = true
executeRuleOnStart(def, key, rule, lowerValue, compactValue)
elseif ruleMatched and hitboxWasActive then
if type(rule.onTick) == "function" then
rule.onTick(def, lowerValue, compactValue)
end
elseif not ruleMatched and hitboxWasActive then
info.hitboxActive = false
end
end
end
end
end
function UsingSkillWatcher.queueUpdate(def)
if not def then
return
end
UsingSkillWatcher.update(def)
end
local function detachUsingSkill(obj)
local def = usingSkillDefs[obj]
if not def then
return
end
local lastSkillValue = def.lastSkillValue or ""
if lastSkillValue ~= "" then
local playerName, isLocal = getSkillLogIdentity(def)
local tag = isLocal and "Local" or "Other"
local duration = def.lastSkillStart and (clock() - def.lastSkillStart) or nil
if def.lastSkillEntry then
updateSkillEntryLabel(def.lastSkillEntry, tag, playerName, "End", lastSkillValue, duration, "lasted")
end
if Library and Library.AddUsingSkillEntry then
Library:AddUsingSkillEntry(playerName, lastSkillValue, isLocal, "End", duration)
end
def.lastSkillValue = ""
def.lastSkillStart = nil
def.lastSkillEntry = nil
def.lastSkillUpdateAt = nil
def.lastSkillToken = (def.lastSkillToken or 0) + 1
end
if def.states then
for key, active in pairs(def.states) do
if active then
def.states[key] = false
local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key]
if rule and type(rule.onStop) == "function" then
rule.onStop(def)
end
end
end
end
usingSkillDefs[obj] = nil
if def.valueConn then
def.valueConn:Disconnect()
end
if def.ancestryConn then
def.ancestryConn:Disconnect()
end
end
local function attachUsingSkill(obj)
if usingSkillDefs[obj] ~= nil then
return false
end
local def = { obj = obj, player = nil, character = nil, lastValue = nil, states = {} }
usingSkillDefs[obj] = def
if obj.GetPropertyChangedSignal then
def.valueConn = obj:GetPropertyChangedSignal("Value"):Connect(function()
UsingSkillWatcher.queueUpdate(def)
end)
else
def.valueConn = obj.Changed:Connect(function()
UsingSkillWatcher.queueUpdate(def)
end)
end
def.ancestryConn = obj.AncestryChanged:Connect(function()
if not obj:IsDescendantOf(game) then
detachUsingSkill(obj)
return
end
UsingSkillWatcher.queueUpdate(def)
end)
UsingSkillWatcher.queueUpdate(def)
return true
end
local function onDescendantAdded(descendant)
if descendant.Name == "UsingSkill" and descendant:IsA("ValueBase") then
attachUsingSkill(descendant)
end
end
local function onDescendantRemoving(descendant)
if usingSkillDefs[descendant] ~= nil then
detachUsingSkill(descendant)
end
end
local liveInstance = nil
local liveAddedConn = nil
local liveRemovingConn = nil
local liveChildAddedConn = nil
local liveChildRemovedConn = nil
local function clearLiveState()
for obj in pairs(usingSkillDefs) do
detachUsingSkill(obj)
end
end
local function attachLive(live)
if liveInstance == live then
return
end
if liveAddedConn then
liveAddedConn:Disconnect()
liveAddedConn = nil
end
if liveRemovingConn then
liveRemovingConn:Disconnect()
liveRemovingConn = nil
end
liveInstance = live
if not live then
clearLiveState()
return
end
liveAddedConn = live.DescendantAdded:Connect(onDescendantAdded)
liveRemovingConn = live.DescendantRemoving:Connect(onDescendantRemoving)
for _, descendant in ipairs(live:GetDescendants()) do
onDescendantAdded(descendant)
end
end
function UsingSkillWatcher.init()
attachLive(workspaceService:FindFirstChild("Live"))
if not pollActive then
pollActive = true
pollTask = task.spawn(LPH_NO_VIRTUALIZE(function()
while pollActive do
local interval = NO_HITBOX_ONLY and noHitboxSkillPollSeconds() or pollInterval
task.wait(interval)
for _, def in pairs(usingSkillDefs) do
UsingSkillWatcher.update(def)
end
end
end))
end
liveChildAddedConn = workspaceService.ChildAdded:Connect(function(child)
if child.Name == "Live" then
attachLive(child)
end
end)
liveChildRemovedConn = workspaceService.ChildRemoved:Connect(function(child)
if child == liveInstance then
attachLive(nil)
end
end)
end
skillDelaySeconds = function()
if NO_HITBOX_ONLY then
return 0
end
local biased = Defender.rdelay() + noHitboxReactionBiasSeconds()
return math.clamp(math.max(0, biased), 0, MAX_NO_HITBOX_DELAY)
end
local function getSkillContext(def)
if not def or not def.character then
return nil
end
local ctx = skillContext[def.character]
if not ctx then
ctx = { lockUntil = 0, lockPriority = 0, lastSkillAt = {} }
skillContext[def.character] = ctx
end
return ctx
end
runSkillAction = function(def, key, rule, lowerValue, compactValue, actionFn)
local _ = compactValue
if not Configuration.expectToggleValue("EnableAutoDefense") then
return
end
local ctx = getSkillContext(def)
if not ctx then
return
end
local now = clock()
local distance = hitboxDefender:distance(def.character) or math.huge
local value = tostring(lowerValue or "")
local threat = (rule and tonumber(rule.priority)) or 1
if distance <= 16 then
threat = threat + 2.6
elseif distance <= 30 then
threat = threat + 1.8
elseif distance <= 50 then
threat = threat + 1.1
elseif distance <= 75 then
threat = threat + 0.6
else
threat = threat + 0.2
end
if value:find("grab", 1, true) or value:find("rush", 1, true) or value:find("combo", 1, true) then
threat = threat + 1.4
elseif value:find("barrage", 1, true) or value:find("gatling", 1, true) or value:find("onslaught", 1, true) then
threat = threat + 1.1
elseif value:find("meteor", 1, true) or value:find("final", 1, true) or value:find("ult", 1, true) then
threat = threat + 1.2
end
local prio = 1
local cdMs = 75
local lockSeconds = (rule and rule.lockSeconds) or SKILL_LOCK_BASE
if threat >= 4.8 then
prio = 3
cdMs = 35
lockSeconds = math.max(lockSeconds, 0.10)
elseif threat >= 3.2 then
prio = 2
cdMs = 52
lockSeconds = math.max(lockSeconds, 0.08)
else
prio = 1
cdMs = 75
lockSeconds = math.max(lockSeconds, 0.06)
end
if rule and type(rule.priority) == "number" then
prio = math.max(prio, rule.priority)
end
if rule and type(rule.cooldownMs) == "number" then
cdMs = math.max(0, rule.cooldownMs)
end
if not NO_HITBOX_ONLY and ctx.lockUntil and now < ctx.lockUntil and prio < (ctx.lockPriority or 0) then
return
end
local last = ctx.lastSkillAt[key]
if last and now - last < (cdMs / 1000) then
return
end
ctx.lastSkillAt[key] = now
ctx.lockPriority = prio
ctx.lockUntil = now + (NO_HITBOX_ONLY and 0.03 or lockSeconds)
local delay = 0
if not NO_HITBOX_ONLY then
delay = rule.adaptiveDelay == false and 0 or skillDelaySeconds()
end
if delay > 0 then
task.delay(delay, function()
if not Configuration.expectToggleValue("EnableAutoDefense") then
return
end
local prev = activeSkillDef
activeSkillDef = def
actionFn()
activeSkillDef = prev
end)
else
local prev = activeSkillDef
activeSkillDef = def
actionFn()
activeSkillDef = prev
end
end
fireNoHitbox = function(def, key, rule, lowerValue, compactValue)
if not def or not rule or type(rule.onStart) ~= "function" then
return
end
def.stateInfo = def.stateInfo or {}
local info = def.stateInfo[key]
if not info then
info = { holdUntil = 0 }
def.stateInfo[key] = info
end
local now = clock()
local cooldown = rule.nhcd or NO_HITBOX_COOLDOWN
if info.nhLast and now - info.nhLast < cooldown then
return
end
info.nhLast = now
local function safeStart()
local ok, err = pcall(function()
executeDefensiveAction(def, rule, function()
return rule.onStart(def, lowerValue, compactValue)
end)
end)
if not ok then
Logger.warn("UsingSkill onStart failed: %s", tostring(err))
end
end
if runSkillAction then
runSkillAction(def, key, rule, lowerValue, compactValue, safeStart)
else
safeStart()
end
end
function UsingSkillWatcher.detach()
if liveAddedConn then
liveAddedConn:Disconnect()
liveAddedConn = nil
end
if liveRemovingConn then
liveRemovingConn:Disconnect()
liveRemovingConn = nil
end
if liveChildAddedConn then
liveChildAddedConn:Disconnect()
liveChildAddedConn = nil
end
if liveChildRemovedConn then
liveChildRemovedConn:Disconnect()
liveChildRemovedConn = nil
end
clearLiveState()
liveInstance = nil
InputClient.block(false)
pollActive = false
pollTask = nil
end
UsingSkillWatcher._allowActions = allowActions
UsingSkillWatcher._passesDistance = passesDistance
UsingSkillWatcher._passesHitboxRaw = passesHitboxRaw
return UsingSkillWatcher
end)
-- MODULE: Features/Combat/M1AutoTrade
defineModule("Features/Combat/M1AutoTrade", function()
local M1AutoTrade = {}
local Players = game:GetService("Players")
local userInputService = game:GetService("UserInputService")
local workspaceService = game:GetService("Workspace")
local Configuration = require("Utility/Configuration")
local InputClient = require("Game/InputClient")
local Table = require("Utility/Table")
local AutoCombo = require("Features/Combat/AutoCombo")
local Logger = require("Utility/Logger")
local lastTradeAt = {}
local TRADE_COOLDOWN = 0.15
local METERS_TO_STUDS = 3.57
local lastHitObj = nil
local lastHitConn = nil
local lastHitDescConn = nil
local lastHitAt = 0
local lastHitTarget = nil
local localM1WindowStart = 0
local localM1WindowUntil = 0
local localM1Token = 0
local LOCAL_M1_RANGE_STUDS = 15
local localInitDone = false
local clearingLastHit = false
local function safeLower(value)
local lowerFn = string and string.lower
if lowerFn then
return lowerFn(tostring(value or ""))
end
return tostring(value or "")
end
local function isLikelyM1Name(name)
local lowered = safeLower(name)
if lowered == "" then
return false
end
if lowered == "m1" then
return true
end
if lowered:find("m1_")
or lowered:find("_m1")
or lowered:find(" m1")
or lowered:find("m1 ")
or lowered:find("-m1")
or lowered:find("m1-")
then
return true
end
if lowered:find("light") or lowered:find("basic") or lowered:find("jab") then
return true
end
return false
end
local function isLocalUsingSkill()
local localCharacter = Players.LocalPlayer and Players.LocalPlayer.Character
if not localCharacter then
return false
end
local state = localCharacter:GetAttribute("CurrentState")
if state == "Skill" then
return true
end
local skillValue = localCharacter:FindFirstChild("UsingSkill", true)
if skillValue and skillValue:IsA("ValueBase") then
local raw = skillValue.Value
if type(raw) == "string" then
local value = safeLower(raw)
if value ~= "" and value ~= "none" and value ~= "idle" then
return true
end
end
end
return false
end
local function resolveLastHit(character)
if not character then
return nil
end
local direct = character:FindFirstChild("LastHit")
if direct and direct:IsA("ValueBase") then
return direct
end
local descendant = character:FindFirstChild("LastHit", true)
if descendant and descendant:IsA("ValueBase") then
return descendant
end
return nil
end
local clearValueBase
local listCloseTargets
clearValueBase = function(valueObj)
if not valueObj or not valueObj:IsA("ValueBase") then
return
end
pcall(function()
if valueObj:IsA("ObjectValue") then
valueObj.Value = nil
elseif valueObj:IsA("StringValue") then
valueObj.Value = ""
elseif valueObj:IsA("BoolValue") then
valueObj.Value = false
elseif valueObj:IsA("NumberValue") or valueObj:IsA("IntValue") then
valueObj.Value = 0
else
valueObj.Value = nil
end
end)
end
local function startComboIfReady()
if not Configuration.expectToggleValue("EnableAutoCombo") then
return false
end
if not Configuration.expectToggleValue("AutoComboOnM1Hit") then
return false
end
if AutoCombo and AutoCombo.isRunning and AutoCombo.isRunning() then
return false
end
if isLocalUsingSkill() then
return false
end
local profileName = Configuration.expectOptionValue("AutoComboProfile") or ""
if profileName == "" then
if Logger and Logger.longNotify then
Logger.longNotify("Please select an Auto-Combo profile.")
end
return false
end
AutoCombo.run(profileName)
return true
end
local function openLocalM1Window()
if not Configuration.expectToggleValue("EnableAutoCombo") then
return
end
if not Configuration.expectToggleValue("AutoComboOnM1Hit") then
return
end
if AutoCombo and AutoCombo.isRunning and AutoCombo.isRunning() then
return
end
if isLocalUsingSkill() then
return
end
local holdMs = Configuration.expectOptionValue("AutoComboOnM1HoldMs") or 250
local holdSeconds = math.max(holdMs, 10) / 1000
local now = os.clock()
localM1WindowStart = now
localM1WindowUntil = now + holdSeconds
local targets = listCloseTargets(LOCAL_M1_RANGE_STUDS)
if #targets == 0 then
return
end
local snapshot = {}
for _, target in ipairs(targets) do
snapshot[target.character] = target.humanoid.Health
end
localM1Token = localM1Token + 1
local token = localM1Token
local deadline = localM1WindowUntil
task.spawn(function()
while os.clock() <= deadline do
if token ~= localM1Token then
return
end
if AutoCombo and AutoCombo.isRunning and AutoCombo.isRunning() then
return
end
if isLocalUsingSkill() then
return
end
for _, target in ipairs(listCloseTargets(LOCAL_M1_RANGE_STUDS)) do
local prev = snapshot[target.character]
if prev and target.humanoid.Health < prev then
startComboIfReady()
return
end
end
task.wait(0.03)
end
end)
end
local function handleLastHitChanged()
if clearingLastHit then
return
end
local obj = lastHitObj
if not obj then
return
end
local raw = nil
local ok = pcall(function()
raw = obj.Value
end)
if not ok then
return
end
if raw == nil or raw == "" then
return
end
lastHitTarget = raw
lastHitAt = os.clock()
clearingLastHit = true
clearValueBase(obj)
clearingLastHit = false
end
local function isM1Block03(timing)
if not timing or timing.tag ~= "M1" then
return false
end
local startBlock = false
local endBlock03 = false
for _, action in next, timing.actions._data do
local atype = tostring(action._type or "")
if atype == "Start Block" then
startBlock = true
elseif atype == "End Block" then
local whenMs = tonumber(action._when) or 0
if whenMs == 300 then
endBlock03 = true
end
end
end
return startBlock and endBlock03
end
local function isAlly(player)
local localPlayer = Players.LocalPlayer
if not localPlayer or not player then
return false
end
local ok, status = pcall(localPlayer.GetFriendStatus, localPlayer, player)
if not ok then
return false
end
return status == Enum.FriendStatus.Friend
end
listCloseTargets = function(maxDistanceStuds)
local ents = workspaceService:FindFirstChild("Live")
if not ents then
return {}
end
local localCharacter = Players.LocalPlayer and Players.LocalPlayer.Character
if not localCharacter then
return {}
end
local localRootPart = localCharacter:FindFirstChild("HumanoidRootPart") or localCharacter:FindFirstChild("Torso")
if not localRootPart then
return {}
end
local targets = {}
for _, entity in next, ents:GetChildren() do
if entity == localCharacter then
continue
end
local playerFromCharacter = Players:GetPlayerFromCharacter(entity)
if playerFromCharacter and Configuration.expectToggleValue("IgnorePlayers") then
continue
end
if playerFromCharacter and isAlly(playerFromCharacter) and Configuration.expectToggleValue("IgnoreAllies") then
continue
end
local humanoid = entity:FindFirstChildWhichIsA("Humanoid")
if not humanoid or humanoid.Health <= 0 then
continue
end
local rootPart = entity:FindFirstChild("HumanoidRootPart") or entity:FindFirstChild("Torso")
if not rootPart then
continue
end
local distance = (rootPart.Position - localRootPart.Position).Magnitude
if distance <= maxDistanceStuds then
targets[#targets + 1] = {
character = entity,
humanoid = humanoid,
root = rootPart,
distance = distance,
}
end
end
return targets
end
local function bestTargets()
local ents = workspaceService:FindFirstChild("Live")
if not ents then
return {}
end
local localCharacter = Players.LocalPlayer and Players.LocalPlayer.Character
if not localCharacter then
return {}
end
local localRootPart = localCharacter:FindFirstChild("HumanoidRootPart")
if not localRootPart then
return {}
end
local currentCamera = workspaceService.CurrentCamera
if not currentCamera then
return {}
end
local targets = {}
local maxDistance = Configuration.expectOptionValue("M1TradeDistanceHit") or 11.1
maxDistance = maxDistance * METERS_TO_STUDS
for _, entity in next, ents:GetChildren() do
if entity == localCharacter then
continue
end
local playerFromCharacter = Players:GetPlayerFromCharacter(entity)
if not playerFromCharacter then
continue
end
if playerFromCharacter and Configuration.expectToggleValue("IgnorePlayers") then
continue
end
local humanoid = entity:FindFirstChildWhichIsA("Humanoid")
if not humanoid or humanoid.Health <= 0 then
continue
end
local rootPart = entity:FindFirstChild("HumanoidRootPart")
if not rootPart then
continue
end
local distance = (rootPart.Position - localRootPart.Position).Magnitude
if distance > maxDistance then
continue
end
if playerFromCharacter and isAlly(playerFromCharacter) and Configuration.expectToggleValue("IgnoreAllies") then
continue
end
local mousePosition = userInputService:GetMouseLocation()
local unitRay = workspaceService.CurrentCamera:ScreenPointToRay(mousePosition.X, mousePosition.Y)
local distanceToCrosshair = unitRay:Distance(rootPart.Position)
local fieldOfViewToEntity = currentCamera.CFrame.LookVector:Dot((localRootPart.Position - rootPart.Position).Unit)
targets[#targets + 1] = {
character = entity,
root = rootPart,
humanoid = humanoid,
dc = distanceToCrosshair,
fov = fieldOfViewToEntity,
du = distance,
}
end
local sortType = Configuration.expectOptionValue("M1TradeSelectionType") or "Closest In Distance"
if sortType == "Closest To Crosshair" then
table.sort(targets, function(a, b)
return a.dc < b.dc
end)
elseif sortType == "Least Health" then
table.sort(targets, function(a, b)
return a.humanoid.Health < b.humanoid.Health
end)
else
table.sort(targets, function(a, b)
return a.du < b.du
end)
end
local maxTargets = Configuration.expectOptionValue("M1TradeMaxTargets") or 1
return Table.slice(targets, 1, maxTargets)
end
local function isAllowedTarget(entity)
for _, target in next, bestTargets() do
if target.character == entity then
return true
end
end
return false
end
local function doM1Trade()
local holdMs = Configuration.expectOptionValue("M1TradeBlockHoldMs") or 160
local waitMs = Configuration.expectOptionValue("M1TradeBlockWaitMs") or 0
local holdSeconds = math.max(holdMs, 10) / 1000
local waitSeconds = math.max(waitMs, 0) / 1000
if waitSeconds > 0 then
task.wait(waitSeconds)
end
InputClient.blockTap(holdSeconds)
if Configuration.expectToggleValue("M1TradeManualOnly") then
return
end
task.delay(holdSeconds + 0.005, function()
local oneTap = Configuration.expectToggleValue("M1TradeOneAutoM1")
local totalClicks = oneTap and 1 or 5
local gapMs = Configuration.expectOptionValue("M1TradeClickGapMs") or 40
local holdMs = Configuration.expectOptionValue("M1TradeClickHoldMs") or 30
local gapSeconds = math.max(gapMs, 10) / 1000
local holdSecondsClick = math.max(holdMs, 10) / 1000
if totalClicks == 1 then
if InputClient._m1Down or InputClient._m1KeyDown then
InputClient.m1Release()
task.wait(0.02)
end
InputClient.m1Tap(holdSecondsClick)
InputClient.m1Release()
return
end
for i = 1, totalClicks do
InputClient.m1Tap(holdSecondsClick)
if i < totalClicks then
task.wait(gapSeconds)
end
end
InputClient.m1Release()
end)
end
function M1AutoTrade.handle(defender, timing, distance)
if not Configuration.expectToggleValue("EnableM1AutoTrade") then
return
end
if not isM1Block03(timing) then
return
end
if not defender or not defender.entity then
return
end
local dist = distance or defender:distance(defender.entity) or math.huge
local maxDistance = (Configuration.expectOptionValue("M1TradeDistanceHit") or 11.1) * METERS_TO_STUDS
if dist > maxDistance then
return
end
if not isAllowedTarget(defender.entity) then
return
end
local last = lastTradeAt[defender.entity]
local now = os.clock()
if last and now - last < TRADE_COOLDOWN then
return
end
lastTradeAt[defender.entity] = now
doM1Trade()
end
function M1AutoTrade.handleEntity(timing, entity, distance)
if not Configuration.expectToggleValue("EnableM1AutoTrade") then
return
end
if not isM1Block03(timing) then
return
end
if not entity then
return
end
local dist = distance or math.huge
local maxDistance = (Configuration.expectOptionValue("M1TradeDistanceHit") or 11.1) * METERS_TO_STUDS
if dist > maxDistance then
return
end
if not isAllowedTarget(entity) then
return
end
local last = lastTradeAt[entity]
local now = os.clock()
if last and now - last < TRADE_COOLDOWN then
return
end
lastTradeAt[entity] = now
doM1Trade()
end
function M1AutoTrade.handleLocalM1(timing, track)
local isM1 = timing and timing.tag == "M1"
if not isM1 and not timing and track then
if isLikelyM1Name(track.Name) then
isM1 = true
else
local anim = track.Animation
if anim and isLikelyM1Name(anim.Name) then
isM1 = true
else
local aid = (anim and anim.AnimationId) or track.AnimationId
if isLikelyM1Name(aid) then
isM1 = true
end
end
end
end
if not isM1 then
return
end
openLocalM1Window()
end
function M1AutoTrade.init()
if localInitDone then
return
end
localInitDone = true
local localPlayer = Players.LocalPlayer
if not localPlayer then
Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
localPlayer = Players.LocalPlayer
end
local liveChildConn = nil
local function detachLastHit()
if lastHitConn then
lastHitConn:Disconnect()
lastHitConn = nil
end
if lastHitDescConn then
lastHitDescConn:Disconnect()
lastHitDescConn = nil
end
lastHitObj = nil
lastHitAt = 0
lastHitTarget = nil
localM1WindowStart = 0
localM1WindowUntil = 0
end
local function attachLastHit(character)
detachLastHit()
if not character then
return
end
local hit = resolveLastHit(character)
if not hit then
local live = workspaceService:FindFirstChild("Live")
local liveChar = live and localPlayer and live:FindFirstChild(localPlayer.Name)
if liveChar and liveChar ~= character then
hit = resolveLastHit(liveChar)
if hit then
character = liveChar
end
end
end
if hit then
lastHitObj = hit
if hit.GetPropertyChangedSignal then
lastHitConn = hit:GetPropertyChangedSignal("Value"):Connect(handleLastHitChanged)
else
lastHitConn = hit.Changed:Connect(handleLastHitChanged)
end
end
lastHitDescConn = character.DescendantAdded:Connect(function(desc)
if lastHitObj then
return
end
if desc.Name == "LastHit" and desc:IsA("ValueBase") then
lastHitObj = desc
if desc.GetPropertyChangedSignal then
lastHitConn = desc:GetPropertyChangedSignal("Value"):Connect(handleLastHitChanged)
else
lastHitConn = desc.Changed:Connect(handleLastHitChanged)
end
end
end)
end
local function watchLive(live)
if liveChildConn then
liveChildConn:Disconnect()
liveChildConn = nil
end
if not live then
return
end
liveChildConn = live.ChildAdded:Connect(function(child)
if not localPlayer or not child then
return
end
if child.Name == localPlayer.Name then
attachLastHit(child)
end
end)
local existing = live:FindFirstChild(localPlayer.Name)
if existing and not lastHitObj then
attachLastHit(existing)
end
end
localPlayer.CharacterAdded:Connect(attachLastHit)
localPlayer.CharacterRemoving:Connect(function()
detachLastHit()
end)
if localPlayer.Character then
attachLastHit(localPlayer.Character)
end
watchLive(workspaceService:FindFirstChild("Live"))
workspaceService.ChildAdded:Connect(function(child)
if child and child.Name == "Live" then
watchLive(child)
end
end)
workspaceService.ChildRemoved:Connect(function(child)
if child and child.Name == "Live" then
if liveChildConn then
liveChildConn:Disconnect()
liveChildConn = nil
end
end
end)
userInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
openLocalM1Window()
return
end
if input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.ButtonR2 then
openLocalM1Window()
end
end)
end
return M1AutoTrade
end)
-- MODULE: Features/Combat/AutoCombo
defineModule("Features/Combat/AutoCombo", function()
local AutoCombo = {}
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local ContextActionService = game:GetService("ContextActionService")
local Configuration = require("Utility/Configuration")
local Logger = require("Utility/Logger")
local Filesystem = require("Utility/Filesystem")
local Serializer = require("Utility/Serializer")
local Deserializer = require("Utility/Deserializer")
local InputClient = require("Game/InputClient")
local Targeting = require("Features/Combat/Targeting")
local TaskSpawner = require("Utility/TaskSpawner")
local MOVE_BLOCK_ACTION = "AutoCombo_BlockMove"
local moveBlockActive = false
local function setMoveBlock(enabled)
if not ContextActionService then
return
end
if enabled then
if moveBlockActive then
return
end
moveBlockActive = true
pcall(function()
ContextActionService:BindActionAtPriority(
MOVE_BLOCK_ACTION,
function()
return Enum.ContextActionResult.Sink
end,
false,
Enum.ContextActionPriority.High.Value,
Enum.KeyCode.W,
Enum.KeyCode.A,
Enum.KeyCode.S,
Enum.KeyCode.D,
Enum.KeyCode.Up,
Enum.KeyCode.Down,
Enum.KeyCode.Left,
Enum.KeyCode.Right
)
end)
else
if not moveBlockActive then
return
end
moveBlockActive = false
pcall(function()
ContextActionService:UnbindAction(MOVE_BLOCK_ACTION)
end)
end
end
local fs = Filesystem.new("CottomHub-AutoCombos")
local comboState = {
token = 0,
running = false,
currentStep = nil,
lockedTarget = nil,
lastTarget = nil,
}
local function sanitizeName(name)
name = tostring(name or "")
name = name:gsub("[\\/%:%*%?\"<>|]", "")
name = name:gsub("^%s+", ""):gsub("%s+$", "")
return name
end
local function safeNumber(value, fallback)
local num = tonumber(value)
if num == nil then
return fallback
end
return num
end
local function optionValue(key, fallback)
local val = Configuration.expectOptionValue(key)
if type(val) == "number" then
return val
end
return fallback
end
local function normalizeOffset(value)
if typeof(value) == "Vector3" then
return { x = value.X, y = value.Y, z = value.Z }
end
if type(value) == "table" then
local x = tonumber(value.x or value.X or value[1]) or 0
local y = tonumber(value.y or value.Y or value[2]) or 0
local z = tonumber(value.z or value.Z or value[3]) or 0
return { x = x, y = y, z = z }
end
return { x = 0, y = 0, z = 0 }
end
local function vectorFromOffset(value)
local offset = normalizeOffset(value)
return Vector3.new(offset.x or 0, offset.y or 0, offset.z or 0)
end
local function coerceStep(step)
if type(step) ~= "table" then
return nil
end
local stype = tostring(step.type or "")
if stype == "" then
return nil
end
local out = { type = stype }
if stype == "Skill" then
out.slot = math.clamp(safeNumber(step.slot, 1), 1, 4)
if type(step.toolName) == "string" and step.toolName ~= "" then
out.toolName = step.toolName
end
local delayAfter = tonumber(step.delayAfterMs)
if delayAfter ~= nil then
out.delayAfterMs = delayAfter
end
elseif stype == "Wait" then
out.durationMs = safeNumber(step.durationMs, 0)
elseif stype == "M1" then
out.holdMs = safeNumber(step.holdMs, 60)
out["repeat"] = math.max(1, safeNumber(step["repeat"], 1))
out.gapMs = safeNumber(step.gapMs, 60)
elseif stype == "Uptilt" then
out.jumpHoldMs = safeNumber(step.jumpHoldMs, optionValue("AutoComboUptiltJumpHoldMs", 120))
out.m1HoldMs = safeNumber(step.m1HoldMs, optionValue("AutoComboUptiltM1HoldMs", 60))
out.delayBeforeM1Ms = safeNumber(step.delayBeforeM1Ms, 30)
elseif stype == "Upfling" then
out.jumpHoldMs = math.max(0, safeNumber(step.jumpHoldMs, 400))
out.m1HoldMs = math.max(10, safeNumber(step.m1HoldMs, 230))
elseif stype == "JumpHold" then
out.holdMs = math.max(0, safeNumber(step.holdMs, 400))
elseif stype == "Jump" then
out.count = math.max(1, safeNumber(step.count, 1))
out.holdMs = math.max(10, safeNumber(step.holdMs, 50))
out.gapMs = math.max(0, safeNumber(step.gapMs, 120))
elseif stype == "TweenToHead" then
out.maxDistance = safeNumber(step.maxDistance, optionValue("AutoComboTweenMaxDistance", 15))
local durationSeconds = tonumber(step.durationSeconds or step.duration or step.tweenDuration)
if durationSeconds and durationSeconds > 0 then
out.durationSeconds = durationSeconds
end
out.alignFeet = step.alignFeet == true
local speed = tonumber(step.speed)
if not speed then
local legacyTime = tonumber(step.tweenTime)
if legacyTime and legacyTime > 0 and out.maxDistance and out.maxDistance > 0 then
speed = out.maxDistance / legacyTime
end
end
out.speed = safeNumber(speed, optionValue("AutoComboTweenSpeed", 60))
out.offset = normalizeOffset(step.offset)
elseif stype == "MoveToTarget" then
local duration = tonumber(step.durationSeconds)
if not duration then
local legacyTimeout = tonumber(step.timeoutMs)
if legacyTimeout then
duration = legacyTimeout / 1000
end
end
out.durationSeconds = safeNumber(duration, optionValue("AutoComboMoveToDurationSeconds", 1.2))
out.stopDistance = safeNumber(step.stopDistance, optionValue("AutoComboMoveToStopDistance", 4))
out.useSprint = step.useSprint == true
elseif stype == "Dash" then
local dir = tostring(step.dir or "right")
if dir ~= "left" and dir ~= "right" and dir ~= "forward" and dir ~= "back" then
dir = "right"
end
out.dir = dir
elseif stype == "CameraPitch" then
out.pitchDegrees = safeNumber(step.pitchDegrees, -45)
out.durationMs = math.max(0, safeNumber(step.durationMs, 0))
out.holdMs = math.max(0, safeNumber(step.holdMs, 0))
out.resetMs = math.max(0, safeNumber(step.resetMs, 0))
end
out.parallel = step.parallel == true
if step.delayAfterMs ~= nil then
out.delayAfterMs = safeNumber(step.delayAfterMs, 0)
end
return out
end
local function coerceProfile(data)
if type(data) ~= "table" then
return nil
end
local profile = {
name = tostring(data.name or "Unnamed"),
steps = {},
meta = type(data.meta) == "table" and data.meta or {},
}
if type(data.steps) == "table" then
for _, step in ipairs(data.steps) do
local normalized = coerceStep(step)
if normalized then
table.insert(profile.steps, normalized)
end
end
end
return profile
end
function AutoCombo.listProfiles()
local ok, files = pcall(fs.list, fs)
if not ok or type(files) ~= "table" then
return {}
end
local list = {}
for _, file in ipairs(files) do
local name = tostring(file)
if name:sub(-5) == ".json" then
table.insert(list, name:sub(1, -6))
end
end
table.sort(list)
return list
end
function AutoCombo.loadProfile(name)
local clean = sanitizeName(name)
if clean == "" then
return nil, "empty"
end
local ok, content = pcall(fs.read, fs, clean .. ".json")
if not ok then
return nil, "missing"
end
local ok2, parsed = pcall(Deserializer.unmarshal_one, content)
if not ok2 then
return nil, "parse"
end
local profile = coerceProfile(parsed)
if not profile then
return nil, "invalid"
end
profile.name = clean
return profile
end
function AutoCombo.saveProfile(name, data)
local clean = sanitizeName(name)
if clean == "" then
return false, "empty"
end
local profile = coerceProfile(data or {})
if not profile then
return false, "invalid"
end
profile.name = clean
profile.meta = profile.meta or {}
profile.meta.updatedAt = os.time()
if not profile.meta.createdAt then
profile.meta.createdAt = os.time()
end
local ok, payload = pcall(Serializer.marshal, profile)
if not ok then
return false, "serialize"
end
local ok2 = pcall(fs.write, fs, clean .. ".json", payload)
if not ok2 then
return false, "write"
end
return true
end
function AutoCombo.deleteProfile(name)
local clean = sanitizeName(name)
if clean == "" then
return false, "empty"
end
local ok = pcall(fs.delete, fs, clean .. ".json")
if not ok then
return false, "delete"
end
return true
end
function AutoCombo.stop()
comboState.token = (comboState.token or 0) + 1
comboState.running = false
comboState.currentStep = nil
comboState.lockedTarget = nil
comboState.lastTarget = nil
end
function AutoCombo.isRunning()
return comboState.running == true
end
local function isRunning(token)
return comboState.running and token == comboState.token
end
local function isTargetValid(target)
if not target or not target.character or not target.root or not target.humanoid then
return false
end
if target.character.Parent == nil or target.root.Parent == nil then
return false
end
if target.humanoid.Health <= 0 then
return false
end
return true
end
local function selectTarget()
local targets = Targeting.best()
return targets and targets[1] or nil
end
local function resolveTarget(state)
if Configuration.expectToggleValue("AutoComboLockTarget") then
if isTargetValid(state.lockedTarget) then
return state.lockedTarget
end
if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then
state.lockedTarget = selectTarget()
return state.lockedTarget
end
return nil
end
if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then
state.lastTarget = selectTarget()
return state.lastTarget
end
return state.lastTarget
end
local function ensureTargetAlive(state)
if not Configuration.expectToggleValue("AutoComboLockTarget") then
return true
end
if not state.lockedTarget then
if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then
state.lockedTarget = selectTarget()
end
return true
end
if not isTargetValid(state.lockedTarget) then
if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then
state.lockedTarget = selectTarget()
end
if state.lockedTarget and not isTargetValid(state.lockedTarget) then
AutoCombo.stop()
return false
end
end
return true
end
local function getLocalRoot()
local character = Players.LocalPlayer and Players.LocalPlayer.Character
if not character then
return nil, nil, nil
end
local root = character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso")
local humanoid = character:FindFirstChildOfClass("Humanoid")
return character, root, humanoid
end
local function moveHumanoid(humanoid, direction)
if not humanoid or not humanoid.Parent then
return false
end
local moveDirection = direction
if typeof(moveDirection) ~= "Vector3" then
moveDirection = Vector3.zero
end
if moveDirection.Magnitude > 1 then
moveDirection = moveDirection.Unit
end
local ok = pcall(function()
humanoid:Move(moveDirection, false)
end)
return ok
end
local function moveHumanoidTo(humanoid, position)
if not humanoid or not humanoid.Parent then
return false
end
if typeof(position) ~= "Vector3" then
return false
end
local ok = pcall(function()
humanoid:MoveTo(position)
end)
return ok
end
local waitWithCancel = LPH_NO_VIRTUALIZE(function(seconds, token)
if seconds <= 0 then
return true
end
local endTime = os.clock() + seconds
while true do
if not isRunning(token) then
return false
end
local remaining = endTime - os.clock()
if remaining <= 0 then
return true
end
task.wait(math.min(remaining, 0.03))
end
end)
local autoComboHotbarRef = nil
local function resolveAutoComboHotbar()
if autoComboHotbarRef and autoComboHotbarRef.Parent then
return autoComboHotbarRef
end
local playerGui = Players.LocalPlayer and Players.LocalPlayer:FindFirstChild("PlayerGui")
if not playerGui then
return nil
end
local hud = playerGui:FindFirstChild("HUD")
local backpackGui = hud and hud:FindFirstChild("Backpack")
local hotbar = backpackGui and backpackGui:FindFirstChild("Hotbar")
if not hotbar then
for _, inst in ipairs(playerGui:GetDescendants()) do
if inst.Name == "Hotbar" then
hotbar = inst
break
end
end
end
autoComboHotbarRef = hotbar
return hotbar
end
local function resolveAutoComboToolName(slot)
local hotbar = resolveAutoComboHotbar()
if not hotbar then
return nil
end
local slotFrame = hotbar:FindFirstChild(tostring(slot))
if not slotFrame then
return nil
end
local toolNameObj = slotFrame:FindFirstChild("ToolName") or slotFrame:FindFirstChild("Name")
if not toolNameObj then
return nil
end
if toolNameObj:IsA("TextLabel") or toolNameObj:IsA("TextButton") then
return toolNameObj.Text
end
if toolNameObj:IsA("StringValue") then
return toolNameObj.Value
end
return nil
end
local function resolveAutoComboTool(slot)
local backpack = Players.LocalPlayer and Players.LocalPlayer:FindFirstChild("Backpack")
if backpack then
for _, tool in ipairs(backpack:GetChildren()) do
if tool:IsA("Tool") then
local attrSlot = tool:GetAttribute("Slot") or tool:GetAttribute("HotbarSlot") or tool:GetAttribute("Index")
if attrSlot and tonumber(attrSlot) == slot then
return tool
end
local slotValue = tool:FindFirstChild("Slot") or tool:FindFirstChild("Index")
if slotValue and tonumber(slotValue.Value) == slot then
return tool
end
end
end
end
local character = Players.LocalPlayer and Players.LocalPlayer.Character
return character and character:FindFirstChildOfClass("Tool") or nil
end
local function fireSkill(step)
if not InputClient then
return false
end
local slot = math.clamp(safeNumber(step.slot, 1), 1, 4)
local keyMap = {
[1] = Enum.KeyCode.One,
[2] = Enum.KeyCode.Two,
[3] = Enum.KeyCode.Three,
[4] = Enum.KeyCode.Four,
}
local key = keyMap[slot]
if InputClient.tapKey and key then
return InputClient.tapKey(key)
end
return false
end
local doM1 = LPH_NO_VIRTUALIZE(function(step, token)
local repeatCount = math.max(1, safeNumber(step["repeat"], 1))
local holdSeconds = math.max(10, safeNumber(step.holdMs, 60)) / 1000
local gapSeconds = math.max(0, safeNumber(step.gapMs, 60)) / 1000
for i = 1, repeatCount do
if not isRunning(token) then
return
end
InputClient.m1Hold(holdSeconds)
if not waitWithCancel(holdSeconds + (i < repeatCount and gapSeconds or 0), token) then
return
end
end
end)
local doUptilt = LPH_NO_VIRTUALIZE(function(step, token)
local jumpHoldSeconds = math.max(10, safeNumber(step.jumpHoldMs, optionValue("AutoComboUptiltJumpHoldMs", 120))) / 1000
local m1HoldSeconds = math.max(10, safeNumber(step.m1HoldMs, optionValue("AutoComboUptiltM1HoldMs", 60))) / 1000
local delayBeforeM1 = math.max(0, safeNumber(step.delayBeforeM1Ms, 30)) / 1000
InputClient.jumpHold(jumpHoldSeconds)
if delayBeforeM1 > 0 then
if not waitWithCancel(delayBeforeM1, token) then
return
end
end
InputClient.m1Hold(m1HoldSeconds)
waitWithCancel(m1HoldSeconds, token)
end)
local doUpfling = LPH_NO_VIRTUALIZE(function(step, token)
local jumpHoldSeconds = math.max(10, safeNumber(step.jumpHoldMs, 400)) / 1000
local m1HoldSeconds = math.max(10, safeNumber(step.m1HoldMs, 230)) / 1000
InputClient.jumpHold(jumpHoldSeconds)
InputClient.m1Hold(m1HoldSeconds)
waitWithCancel(math.max(jumpHoldSeconds, m1HoldSeconds), token)
end)
local doJumpHold = LPH_NO_VIRTUALIZE(function(step, token)
local holdSeconds = math.max(0, safeNumber(step.holdMs, 400)) / 1000
if holdSeconds <= 0 then
return
end
InputClient.jumpHold(holdSeconds)
waitWithCancel(holdSeconds, token)
end)
local doJump = LPH_NO_VIRTUALIZE(function(step, token)
local count = math.max(1, safeNumber(step.count, 1))
local holdSeconds = math.max(0, safeNumber(step.holdMs, 50)) / 1000
local gapSeconds = math.max(0, safeNumber(step.gapMs, 120)) / 1000
for i = 1, count do
if not isRunning(token) then
return
end
if holdSeconds > 0 then
InputClient.jumpHold(holdSeconds)
if not waitWithCancel(holdSeconds, token) then
return
end
else
InputClient.jump()
end
if i < count and gapSeconds > 0 then
if not waitWithCancel(gapSeconds, token) then
return
end
end
end
end)
local doTween = LPH_NO_VIRTUALIZE(function(step, target, token, state)
if not target or not target.root then
return
end
local character, localRoot, humanoid = getLocalRoot()
if not localRoot then
return
end
local maxDistance = safeNumber(step.maxDistance, optionValue("AutoComboTweenMaxDistance", 15))
local offset = vectorFromOffset(step.offset)
local durationSeconds = tonumber(step.durationSeconds)
local speed = math.max(0, safeNumber(step.speed, optionValue("AutoComboTweenSpeed", 60)))
local function resolveHead()
return target.character and target.character:FindFirstChild("Head") or target.root
end
local head = resolveHead()
if not head then
return
end
local dist = (head.Position - localRoot.Position).Magnitude
if not durationSeconds then
if maxDistance > 0 and dist > maxDistance then
return
end
end
local totalTime = 0
if durationSeconds and durationSeconds > 0 then
totalTime = durationSeconds
elseif speed > 0 then
totalTime = dist / speed
end
if totalTime <= 0 then
return
end
local function computeRelativeOffset(vec)
if not step.relativeOffset then
return vec
end
local fallbackBasis = (localRoot and localRoot.CFrame)
or (target and target.root and target.root.CFrame)
or CFrame.new()
local basis = nil
if step.relativeToLocal then
basis = (localRoot and localRoot.CFrame) or fallbackBasis
else
basis = (target and target.root and target.root.CFrame) or fallbackBasis
end
local forward = Vector3.new(basis.LookVector.X, 0, basis.LookVector.Z)
if forward.Magnitude > 1e-3 then
forward = forward.Unit
else
forward = Vector3.new(0, 0, -1)
end
local right = Vector3.new(basis.RightVector.X, 0, basis.RightVector.Z)
if right.Magnitude > 1e-3 then
right = right.Unit
else
right = Vector3.new(1, 0, 0)
end
return right * vec.X + Vector3.new(0, 1, 0) * vec.Y + forward * vec.Z
end
local function computeOffset(headInstance)
if not step.alignFeet then
return computeRelativeOffset(offset)
end
local character = Players.LocalPlayer and Players.LocalPlayer.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
local hip = humanoid and humanoid.HipHeight or (localRoot.Size.Y / 2)
local headHalf = headInstance and headInstance.Size and headInstance.Size.Y / 2 or 1.5
local baseOffset = offset + Vector3.new(0, headHalf + hip, 0)
return computeRelativeOffset(baseOffset)
end
local start = os.clock()
while os.clock() - start < totalTime do
if not isRunning(token) then
return
end
local liveCharacter, liveRoot, liveHumanoid = getLocalRoot()
if not liveCharacter or not liveRoot then
return
end
character = liveCharacter
localRoot = liveRoot
humanoid = liveHumanoid or humanoid
if not isTargetValid(target) then
if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") and state then
target = resolveTarget(state)
end
if not isTargetValid(target) then
if Configuration.expectToggleValue("AutoComboLockTarget") and state and state.lockedTarget then
AutoCombo.stop()
end
return
end
end
head = resolveHead()
if not head then
return
end
local dest = head.Position + computeOffset(head)
local current = localRoot.Position
local toDest = dest - current
local distance = toDest.Magnitude
if humanoid then
moveHumanoid(humanoid, localRoot.CFrame.LookVector)
end
if distance > 0.001 then
local dt = RunService.RenderStepped:Wait()
local stepDist = speed > 0 and speed * dt or distance
if stepDist > distance then
stepDist = distance
end
local newPos = current + toDest.Unit * stepDist
local rotation = localRoot.CFrame - localRoot.CFrame.Position
localRoot.CFrame = CFrame.new(newPos) * rotation
else
RunService.RenderStepped:Wait()
end
end
if humanoid then
moveHumanoid(humanoid, Vector3.zero)
end
end)
local doMoveTo = LPH_NO_VIRTUALIZE(function(step, target, token, state)
local character, localRoot, humanoid = getLocalRoot()
if not character or not localRoot then
return
end
if not humanoid then
return
end
local duration = math.max(0, safeNumber(step.durationSeconds, optionValue("AutoComboMoveToDurationSeconds", 1.2)))
local stopDistance = math.max(0, safeNumber(step.stopDistance, optionValue("AutoComboMoveToStopDistance", 4)))
local useSprint = step.useSprint == true
local sprintHold = math.max(0, safeNumber(optionValue("AutoComboSprintHoldMs", 350), 350)) / 1000
local repathSeconds = math.max(0.03, safeNumber(step.repathSeconds, 0.06))
if duration <= 0 then
return
end
local movementLocked = false
local function stopMoveTo()
local _, liveRoot, liveHumanoid = getLocalRoot()
if liveHumanoid and liveRoot then
moveHumanoidTo(liveHumanoid, liveRoot.Position)
elseif humanoid and localRoot then
moveHumanoidTo(humanoid, localRoot.Position)
end
end
local function lockMovement()
if movementLocked then
return
end
movementLocked = true
setMoveBlock(true)
end
local function unlockMovement()
if not movementLocked then
return
end
movementLocked = false
setMoveBlock(false)
end
lockMovement()
local start = os.clock()
local lastSprint = 0
local lastPos = target and target.root and target.root.Position or nil
local nextMoveToAt = 0
local lastMoveToPos = nil
while os.clock() - start <= duration do
if not isRunning(token) then
stopMoveTo()
unlockMovement()
return
end
local liveCharacter, liveRoot, liveHumanoid = getLocalRoot()
if not liveCharacter or not liveRoot or not liveHumanoid then
stopMoveTo()
unlockMovement()
return
end
character = liveCharacter
localRoot = liveRoot
humanoid = liveHumanoid
if target and isTargetValid(target) then
state.lastTarget = target
lastPos = target.root.Position
else
target = resolveTarget(state)
if target and isTargetValid(target) then
state.lastTarget = target
lastPos = target.root.Position
else
if Configuration.expectToggleValue("AutoComboLockTarget") and state.lockedTarget then
AutoCombo.stop()
end
stopMoveTo()
unlockMovement()
return
end
end
if lastPos then
local toDest = lastPos - localRoot.Position
local flat = Vector3.new(toDest.X, 0, toDest.Z)
local dist = flat.Magnitude
if stopDistance > 0 and dist <= stopDistance then
break
end
if dist > 1e-3 then
local now = os.clock()
local shouldUpdate = (not lastMoveToPos)
or ((lastMoveToPos - lastPos).Magnitude > 1)
or (now >= nextMoveToAt)
if shouldUpdate then
moveHumanoidTo(humanoid, lastPos)
lastMoveToPos = lastPos
nextMoveToAt = now + repathSeconds
end
end
end
if useSprint and sprintHold > 0 then
local now = os.clock()
if now - lastSprint >= sprintHold * 0.8 then
InputClient.sprintHold(sprintHold)
lastSprint = now
end
end
RunService.Heartbeat:Wait()
end
stopMoveTo()
unlockMovement()
end)
local function executeStep(step, token, state)
if step.type == "Wait" then
waitWithCancel((step.durationMs or 0) / 1000, token)
elseif step.type == "Skill" then
fireSkill(step)
elseif step.type == "M1" then
doM1(step, token)
elseif step.type == "Uptilt" then
doUptilt(step, token)
elseif step.type == "Upfling" then
doUpfling(step, token)
elseif step.type == "JumpHold" then
doJumpHold(step, token)
elseif step.type == "Jump" then
doJump(step, token)
elseif step.type == "TweenToHead" then
local target = resolveTarget(state)
if not target or not isTargetValid(target) then
if Configuration.expectToggleValue("AutoComboLockTarget") and state.lockedTarget then
AutoCombo.stop()
end
return
end
doTween(step, target, token, state)
elseif step.type == "MoveToTarget" then
local target = resolveTarget(state)
if not target or not isTargetValid(target) then
if Configuration.expectToggleValue("AutoComboLockTarget") and state.lockedTarget then
AutoCombo.stop()
end
return
end
doMoveTo(step, target, token, state)
elseif step.type == "Dash" then
local dir = step.dir or "right"
InputClient.dash(dir)
elseif step.type == "CameraPitch" then
local cam = workspace.CurrentCamera
if not cam then
return
end
local pitch0, yaw0, roll0 = cam.CFrame:ToOrientation()
local targetPitch = math.rad(step.pitchDegrees or -45)
local duration = math.max(0, (step.durationMs or 0) / 1000)
local hold = math.max(0, (step.holdMs or 0) / 1000)
local reset = math.max(0, (step.resetMs or 0) / 1000)
local function lerpPitch(fromPitch, toPitch, seconds)
if seconds <= 0 then
cam.CFrame = CFrame.new(cam.CFrame.Position) * CFrame.fromOrientation(toPitch, yaw0, roll0)
return
end
local start = os.clock()
while os.clock() - start < seconds do
if not isRunning(token) then
return
end
local alpha = math.clamp((os.clock() - start) / seconds, 0, 1)
local p = fromPitch + (toPitch - fromPitch) * alpha
cam.CFrame = CFrame.new(cam.CFrame.Position) * CFrame.fromOrientation(p, yaw0, roll0)
RunService.RenderStepped:Wait()
end
end
lerpPitch(pitch0, targetPitch, duration)
if hold > 0 then
if not waitWithCancel(hold, token) then
return
end
end
if reset > 0 then
lerpPitch(targetPitch, pitch0, reset)
end
end
end
local runProfile = LPH_NO_VIRTUALIZE(function(profile, token)
if Configuration.expectToggleValue("AutoComboLockTarget") then
comboState.lockedTarget = selectTarget()
end
for index, step in ipairs(profile.steps or {}) do
if not isRunning(token) then
break
end
if not ensureTargetAlive(comboState) then
break
end
comboState.currentStep = index
local normalized = coerceStep(step)
if normalized then
if normalized.parallel == true then
TaskSpawner.spawn("AutoCombo_Step_" .. tostring(normalized.type), LPH_NO_VIRTUALIZE(function()
if not isRunning(token) then
return
end
executeStep(normalized, token, comboState)
end))
else
executeStep(normalized, token, comboState)
end
end
if not isRunning(token) then
break
end
local delayMs = normalized and normalized.delayAfterMs
if delayMs == nil then
delayMs = optionValue("AutoComboDefaultStepDelayMs", 100)
end
if delayMs and delayMs > 0 then
if not waitWithCancel(delayMs / 1000, token) then
break
end
end
end
if token == comboState.token then
comboState.running = false
comboState.currentStep = nil
comboState.lockedTarget = nil
comboState.lastTarget = nil
if Logger and Logger.notify then
Logger.notify("Auto-Combo finished: %s", profile.name or "Profile")
end
end
end)
function AutoCombo.run(profileName)
if not Configuration.expectToggleValue("EnableAutoCombo") then
return
end
local profile = nil
if type(profileName) == "table" then
profile = coerceProfile(profileName)
else
profile = AutoCombo.loadProfile(profileName)
end
if not profile then
Logger.longNotify("Auto-Combo profile '%s' could not be loaded.", tostring(profileName))
return
end
AutoCombo.stop()
comboState.running = true
comboState.token = (comboState.token or 0) + 1
local token = comboState.token
TaskSpawner.spawn("AutoCombo_Run", LPH_NO_VIRTUALIZE(function()
runProfile(profile, token)
end))
end
return AutoCombo
end)
-- MODULE: Game/Keybinding
defineModule("Game/Keybinding", function()
-- Keybinding module.
local Keybinding = { info = {} }
---@module Utility.Signal
local Signal = require("Utility/Signal")
---@module Utility.Maid
local Maid = require("Utility/Maid")
---@module Utility.TaskSpawner
local TaskSpawner = require("Utility/TaskSpawner")
-- Services.
local players = game:GetService("Players")
local replicatedStorage = game:GetService("ReplicatedStorage")
-- Maids.
local keybindingMaid = Maid.new()
---Refresh keybind data.
local function refreshKeybindData()
local character = players.LocalPlayer.Character or players.LocalPlayer.CharacterAdded:Wait()
repeat
task.wait()
until character:GetAttribute("KeybindsLoaded")
local requests = replicatedStorage:WaitForChild("Requests")
local getKeybindInfo = requests:WaitForChild("GetKeybindsInfo")
for _, keybindGroup in next, getKeybindInfo:InvokeServer() or {} do
for keybindType, keybindCode in next, keybindGroup do
local success, result = pcall(function()
return Enum.KeyCode[keybindCode]
end)
Keybinding.info[keybindType] = success and result or Enum.KeyCode.Unknown
end
end
end
---Initialize Keybinding module.
function Keybinding.init()
local remotes = replicatedStorage:WaitForChild("Remotes")
local sendKeybindInfo = remotes:WaitForChild("SendKeybindInfo")
local sendKeybindInfoEvent = Signal.new(sendKeybindInfo.OnClientEvent)
keybindingMaid:add(sendKeybindInfoEvent:connect("Keybinding_SkiOnClientEvent", refreshKeybindData))
keybindingMaid:add(TaskSpawner.spawn("Keybinding_UpdateKeybinds", refreshKeybindData))
end
---Detach Keybinding module.
function Keybinding.detach()
keybindingMaid:clean()
end
-- Return Keybinding module.
return Keybinding
end)
-- MODULE: Game/PlayerScanning
defineModule("Game/PlayerScanning", function()
-- Player scanning is handled here.
local PlayerScanning = {
scanQueue = {},
scanDataCache = {},
friendCache = {},
waitingForLoad = {},
readyList = {},
scanning = false,
}
---@module Utility.CoreGuiManager
local CoreGuiManager = require("Utility/CoreGuiManager")
---@module Utility.Signal
local Signal = require("Utility/Signal")
---@module Utility.Maid
local Maid = require("Utility/Maid")
---@module Utility.Logger
local Logger = require("Utility/Logger")
---@module Utility.Configuration
local Configuration = require("Utility/Configuration")
-- Services.
local players = game:GetService("Players")
local httpService = game:GetService("HttpService")
local collectionService = game:GetService("CollectionService")
local runService = game:GetService("RunService")
-- Instances.
local moderatorSound = CoreGuiManager.imark(Instance.new("Sound"))
-- Maid.
local playerScanningMaid = Maid.new()
-- Timestamp.
local lastRateLimit = nil
---Fetch name.
local function fetchName(player)
local spoofName = Configuration.expectToggleValue("InfoSpoofing")
and Configuration.expectToggleValue("SpoofOtherPlayers")
return spoofName and "[REDACTED]"
or string.format("(%s) %s", player:GetAttribute("CharacterName") or "Unknown Character Name", player.Name)
end
---Run player scans.
local runPlayerScans = LPH_NO_VIRTUALIZE(function()
local localPlayer = players.LocalPlayer
if not localPlayer then
return
end
for player, _ in next, PlayerScanning.scanQueue do
if shared.Lycoris.dpscanning then
continue
end
if not PlayerScanning.scanDataCache[player] then
local handledSuccess, handledResult = nil, nil
local unhandledSuccess, unhandledResult = pcall(function()
handledSuccess, handledResult = PlayerScanning.getStaffRank(player)
end)
if not unhandledSuccess then
Logger.warn(
"Scan player %s ran into error '%s' while getting staff rank.",
player.Name,
unhandledResult
)
Logger.longNotify("Failed to scan player %s for moderator status.", fetchName(player), unhandledResult)
PlayerScanning.scanQueue[player] = nil
continue
end
if not handledSuccess then
continue
end
if Configuration.expectToggleValue("NotifyMod") and handledResult then
Logger.longNotify("%s is a staff member with the rank '%s' in group.", fetchName(player), handledResult)
if Configuration.expectToggleValue("NotifyModSound") then
moderatorSound.SoundId = "rbxassetid://6045346303"
moderatorSound.PlaybackSpeed = 1
moderatorSound.Volume = Configuration.expectOptionValue("NotifyModSoundVolume") or 10
moderatorSound:Play()
end
end
PlayerScanning.scanDataCache[player] = { staffRank = handledResult }
end
PlayerScanning.scanQueue[player] = nil
PlayerScanning.friendCache[player] = localPlayer:GetFriendStatus(player) == Enum.FriendStatus.Friend
Logger.warn("Player scanning finished scanning %s in queue.", fetchName(player))
end
end)
---Are there moderators in the server?
---@return table
function PlayerScanning.hasModerators()
for _, scanData in next, PlayerScanning.scanDataCache do
if not scanData.staffRank then
continue
end
return true
end
return false
end
---Is a player an ally?
---@param player Player
---@return boolean
function PlayerScanning.isAlly(player)
return PlayerScanning.friendCache[player]
end
---Fetch roblox data.
---@param url string
---@return boolean, string?
local function fetchRobloxData(url)
if lastRateLimit and os.clock() - lastRateLimit <= 30 then
return false, "On rate-limit cooldown."
end
local response = request({
Url = url,
Method = "GET",
Headers = {
["Content-Type"] = "application/json",
},
})
if response.StatusCode == 429 then
Logger.longNotify("Player scanning is being rate-limited and results will be delayed.")
Logger.longNotify("Please stay in the server with caution.")
lastRateLimit = os.clock()
return false, "Rate-limited."
end
if not response then
return error("Failed to fetch Roblox data.")
end
if not response.Success then
return error(
string.format("Failed to successfully fetch Roblox data with status code %i.", response.StatusCode)
)
end
if not response.Body then
return error("Failed to find Roblox data.")
end
return true, httpService:JSONDecode(response.Body)
end
---Get staff rank - nil if they're not a staff.
---@param player Player
---@return boolean, string?
function PlayerScanning.getStaffRank(player)
local responseSuccess, responseData =
fetchRobloxData(("https://groups.roblox.com/v2/users/%i/groups/roles?includeLocked=true"):format(player.UserId))
if not responseSuccess then
return false, responseData
end
local character = player.Character
if character and character:GetAttribute("ContentCreator") then
return true, "Content Creator"
end
for _, groupData in next, responseData.data do
if groupData.group.id ~= 32740991 and groupData.group.id ~= 13077028 then
continue
end
if groupData.role.rank <= 0 then
continue
end
return true, groupData.role.name
end
return true, nil
end
---Update player scanning.
---@note: Request will yield - so we need a debounce to prevent multiple scan loops.
---@note: We must defer the error back to the caller and reset the scanning debounce so errors will not break the scanning loop.
function PlayerScanning.update()
if PlayerScanning.scanning then
return
end
PlayerScanning.scanning = true
local success, result = pcall(runPlayerScans)
PlayerScanning.scanning = false
if success then
return
end
return error(result)
end
---On friend status changed.
---@param player Player
---@param status Enum.FriendStatus
function PlayerScanning.friend(player, status)
PlayerScanning.friendCache[player] = status == Enum.FriendStatus.Friend
end
---On player added.
---@param player Player
function PlayerScanning.onPlayerAdded(player)
if player == players.LocalPlayer then
return
end
PlayerScanning.scanQueue[player] = true
end
---On player removing.
---@param player Player
function PlayerScanning.onPlayerRemoving(player)
PlayerScanning.scanQueue[player] = nil
PlayerScanning.scanDataCache[player] = nil
PlayerScanning.friendCache[player] = nil
PlayerScanning.waitingForLoad[player] = nil
end
---Initialize PlayerScanning.
function PlayerScanning.init()
-- Signals.
local playerAddedSignal = Signal.new(players.PlayerAdded)
local playerRemovingSignal = Signal.new(players.PlayerRemoving)
local renderSteppedSignal = Signal.new(runService.RenderStepped)
local friendStatusChanged = Signal.new(players.LocalPlayer.FriendStatusChanged)
-- Connect events.
playerScanningMaid:add(friendStatusChanged:connect("PlayerScanning_OnFriendStatusChanged", PlayerScanning.friend))
playerScanningMaid:add(renderSteppedSignal:connect("PlayerScanning_Update", PlayerScanning.update))
playerScanningMaid:add(playerAddedSignal:connect("PlayerScanning_OnPlayerAdded", PlayerScanning.onPlayerAdded))
playerScanningMaid:add(
playerRemovingSignal:connect("PlayerScanning_OnPlayerRemoving", PlayerScanning.onPlayerRemoving)
)
-- Run event(s) for existing players.
for _, player in next, players:GetPlayers() do
PlayerScanning.onPlayerAdded(player)
end
end
---Detach PlayerScanning.
function PlayerScanning.detach()
playerScanningMaid:clean()
end
-- Return PlayerScanning module.
return PlayerScanning
end)
-- MODULE: Game/Timings/Action
defineModule("Game/Timings/Action", function()
---@class Action
---@field _type string
---@field _when number When the action will occur in miliseconds. Never access directly.
---@field hitbox Vector3 The hitbox of the action.
---@field shape string The hitbox shape ("Box" or "Ball").
---@field offset Vector3 The hitbox offset.
---@field ihbc boolean Ignore hitbox check.
---@field name string The name of the action.
---@field tp number Time position. Never accessible unless inside of a module or inside of real code. This is never serialized.
local Action = {}
Action.__index = Action
---Getter for when in seconds.
---@return number
function Action:when()
return PP_SCRAMBLE_NUM(self._when) / 1000
end
---Load from partial values.
---@param values table
function Action:load(values)
if typeof(values._type) == "string" then
self._type = values._type
end
if typeof(values.when) == "number" then
self._when = values.when
end
if typeof(values.name) == "string" then
self.name = values.name
end
if typeof(values.hitbox) == "table" then
self.hitbox = Vector3.new(values.hitbox.X or 0, values.hitbox.Y or 0, values.hitbox.Z or 0)
elseif typeof(values.hitbox) == "Vector3" then
self.hitbox = values.hitbox
end
if typeof(values.ihbc) == "boolean" then
self.ihbc = values.ihbc
end
if typeof(values.shape) == "string" then
self.shape = values.shape
end
if typeof(vObfuscated. The code is scrambled, so you can't read what it does. Common for key systems, but take care.
LEAKED COTTOM HUB ABA! FULL SCRIPT

Universal