-- BASKET ASSIST — RAYFIELD -- Applies a normal ballistic launch after a genuine Shoot input. -- It uses the game's own trajectory equation, then removes small horizontal -- drift near the rim and prevents fast shots skipping the scoring detector. local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local UserInputService = game:GetService("UserInputService") local VirtualInputManager = game:GetService("VirtualInputManager") local StarterGui = game:GetService("StarterGui") local RunService = game:GetService("RunService") local player = Players.LocalPlayer local basketballValue = ReplicatedStorage:WaitForChild("Basketball") local enabled = true local stopped = false local shotToken = 0 local shotArmedUntil = 0 local appliedToken = 0 local qualifiedToken = 0 local connections = {} local cleanupCallbacks = {} local AIM_CONE_DEGREES = 18 local FINAL_APPROACH_HEIGHT = 22 local FINAL_APPROACH_RADIUS = 18 local autoBlockEnabled = true local autoBlockPassesEnabled = true local configuredBlockRange = 25 local autoStealEnabled = true local configuredStealRange = 16 local autoDribbleEnabled = true local configuredAutoDribbleRange = 24 local lastAutoDribbleAt = 0 local autoDribbleKeyDown = false local autoDribbleHeldKey -- Extra time after the game's dribble protection. At this first checkpoint the -- script waits 3 ms more for a possible chained-dribble animation before stealing. local configuredStealWaitMs = 190 local STEAL_RECHECK_MS = 3 local nextStealAt = 0 local dribbleArmedCarrier local dribbleFallbackUntil = 0 local dribbleConfirmUntil = 0 local dribbleReleaseDeadline = 0 local lastBlockAt = 0 local blockDetectionToken = 0 local blockFlightHandled = false local pumpSuppressedUntil = setmetatable({}, { __mode = "k" }) local unresolvedWindupUntil = setmetatable({}, { __mode = "k" }) local confirmedPumpAt = setmetatable({}, { __mode = "k" }) local lastBlockTarget local Rayfield local detectCarrierDribbleTrack local sharedEnvironment = type(getgenv) == "function" and getgenv() or _G local previous = sharedEnvironment.__BasketNaturalAim if type(previous) == "table" and type(previous.Stop) == "function" then pcall(previous.Stop) end local function connect(signal, callback) local connection = signal:Connect(callback) table.insert(connections, connection) return connection end local function notify(text) pcall(function() StarterGui:SetCore("SendNotification", { Title = "Basket Assist", Text = text, Duration = 4 }) end) end -- Read the player's real configured Shoot bind when Knit is available. local keybindsController local ballService local ballController local defenseController local movementController local networkController local Zones local Knit pcall(function() Knit = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("Knit")) end) local function resolveGameControllers() if not Knit then return end if not keybindsController then pcall(function() keybindsController = Knit.GetController("KeybindsController") end) end if not ballController then pcall(function() ballController = Knit.GetController("BallController") end) end if not defenseController then pcall(function() defenseController = Knit.GetController("DefenseController") end) end if not movementController then pcall(function() movementController = Knit.GetController("MovementController") end) end if not networkController then pcall(function() networkController = Knit.GetController("Network") end) end if not ballService then pcall(function() ballService = Knit.GetService("BallService") end) end end resolveGameControllers() pcall(function() Zones = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("Tables"):WaitForChild("Zones")) end) local function isShootInput(input) if keybindsController then local ok, result = pcall(keybindsController.IsInputValid, input, "Shoot") if ok then return result == true end end return input.UserInputType == Enum.UserInputType.MouseButton1 or input.KeyCode == Enum.KeyCode.ButtonR2 end -- BallStateClient gives an accurate fallback when the executor does not -- expose isnetworkowner(). No functions are replaced or hooked. local ballState pcall(function() local ballController = ReplicatedStorage:WaitForChild("Controllers"):WaitForChild("BallController") ballState = require(ballController:WaitForChild("BallStateClient")) end) local executorOwns = type(isnetworkowner) == "function" and isnetworkowner or nil local function localOwnsBall(ball) if executorOwns then local ok, result = pcall(executorOwns, ball) if ok then return result == true end end if ballState and type(ballState.localPlayerIsBallNetworkOwner) == "function" then local ok, result = pcall(ballState.localPlayerIsBallNetworkOwner, ballState) if ok then return result == true end end return false end local function localPossessesBall() if ballState and type(ballState.localPlayerPossessesBall) == "function" then local ok, result = pcall(ballState.localPlayerPossessesBall, ballState) if ok then return result == true end end return false end local function getBallPlayer(last) if not ballState then return nil end local method = last and ballState.getLastPlayerToPossessBall or ballState.getPlayerPossessingBall if type(method) ~= "function" then return nil end local ok, result = pcall(method, ballState) return ok and result or nil end local function currentStealCooldown() local zone = player:FindFirstChild("Zone") if zone and Zones and Zones.StealCooldowns then return Zones.StealCooldowns[zone.Value] or 2.35 end return 2.35 end local function releaseAutoDribbleKey() if not autoDribbleKeyDown then return end autoDribbleKeyDown = false local dribbleKey = autoDribbleHeldKey or Enum.KeyCode.Q autoDribbleHeldKey = nil pcall(function() VirtualInputManager:SendKeyEvent(false, dribbleKey, false, game) end) end -- Use the same keyboard route as a real Q press. Calling BallController:Dribble -- from an executor context can poison the controller's private integrity guard, -- after which both manual dribbling and shooting silently stop working. local function requestNativeDribbleInput() if stopped or os.clock() - lastAutoDribbleAt < 0.12 then return false end resolveGameControllers() local dribbleKey = Enum.KeyCode.Q pcall(function() local inputs = keybindsController and keybindsController.GetInputs("Dribble") if inputs and inputs.Keyboard and inputs.Keyboard.EnumType == Enum.KeyCode then dribbleKey = inputs.Keyboard end end) lastAutoDribbleAt = os.clock() local pressed = pcall(function() autoDribbleKeyDown = true autoDribbleHeldKey = dribbleKey VirtualInputManager:SendKeyEvent(true, dribbleKey, false, game) end) if not pressed then autoDribbleKeyDown = false autoDribbleHeldKey = nil return false end task.defer(function() RunService.Heartbeat:Wait() releaseAutoDribbleKey() end) return true end local function horizontalDistanceToSegment(point, segmentStart, segmentEnd) local point2 = Vector3.new(point.X, 0, point.Z) local start2 = Vector3.new(segmentStart.X, 0, segmentStart.Z) local finish2 = Vector3.new(segmentEnd.X, 0, segmentEnd.Z) local segment = finish2 - start2 local lengthSquared = segment:Dot(segment) if lengthSquared < 0.001 then return (point2 - start2).Magnitude end local alpha = math.clamp((point2 - start2):Dot(segment) / lengthSquared, 0, 1) return (point2 - (start2 + segment * alpha)).Magnitude end -- BallService.Steal is sent when the opponent commits to the steal, before the -- roughly half-second lunge finishes. Request the player's configured Dribble -- input so the game's untouched input connection performs every state check. local function reactToEnemySteal(thiefCharacter, targetCFrame) if not autoDribbleEnabled or stopped or os.clock() - lastAutoDribbleAt < 0.08 then return end resolveGameControllers() if not ballController then return end local values = networkController and networkController.CharValues if not localPossessesBall() and not (values and values.HasBall == true) then return end if not thiefCharacter or thiefCharacter == player.Character then return end local thief = Players:GetPlayerFromCharacter(thiefCharacter) if not thief or thief == player or not thief.Team or not player.Team or thief.Team == player.Team then return end local myRoot = player.Character and player.Character:FindFirstChild("HumanoidRootPart") local thiefRoot = thiefCharacter:FindFirstChild("HumanoidRootPart") if not myRoot or not thiefRoot then return end local startPosition = thiefRoot.Position local hasTarget = typeof(targetCFrame) == "CFrame" local endPosition = hasTarget and targetCFrame.Position or startPosition local startDistance = (Vector3.new(myRoot.Position.X, 0, myRoot.Position.Z) - Vector3.new(startPosition.X, 0, startPosition.Z)).Magnitude if hasTarget then if startDistance > configuredAutoDribbleRange or horizontalDistanceToSegment(myRoot.Position, startPosition, endPosition) > 7 then return end elseif startDistance > configuredAutoDribbleRange then return end if values and values.Dribbling == true then return end requestNativeDribbleInput() end local function clearDribbleStealArm() dribbleArmedCarrier = nil dribbleFallbackUntil = 0 dribbleConfirmUntil = 0 dribbleReleaseDeadline = 0 end local function enemyDribbleDuration(owner) local zone = owner and owner:FindFirstChild("Zone") -- BallController uses 0.55 seconds normally and 0.275 while the carrier's -- Shadow zone is active. return zone and zone.Value == "Shadow" and 0.275 or 0.55 end local function armStealAfterEnemyDribble(owner) if not autoStealEnabled or stopped or not owner or owner == player or not owner.Team or not player.Team or owner.Team == player.Team then return end resolveGameControllers() local carrier if ballController and type(ballController.GetPlayerPossessingBall) == "function" then pcall(function() carrier = ballController:GetPlayerPossessingBall() end) end carrier = carrier or getBallPlayer(false) if carrier ~= owner then return end dribbleArmedCarrier = owner local duration = enemyDribbleDuration(owner) dribbleFallbackUntil = os.clock() + duration + configuredStealWaitMs / 1000 dribbleConfirmUntil = dribbleFallbackUntil + STEAL_RECHECK_MS / 1000 dribbleReleaseDeadline = dribbleConfirmUntil + 0.24 -- Only accepted dribbles create a new replicated animation. Every accepted -- chained dribble therefore resets this deadline, including during the final -- 190-to-193 ms confirmation gap. end local function tryAutoSteal() if not autoStealEnabled or stopped then return end resolveGameControllers() if not defenseController or not ballController then return end if not player.Team or player.Team.Name == "Visitor" then return end local carrier if type(ballController.GetPlayerPossessingBall) == "function" then pcall(function() carrier = ballController:GetPlayerPossessingBall() end) end carrier = carrier or getBallPlayer(false) if carrier and detectCarrierDribbleTrack then detectCarrierDribbleTrack(carrier) end if not carrier or carrier == player or not carrier.Team or carrier.Team == player.Team then if dribbleArmedCarrier and carrier ~= dribbleArmedCarrier then clearDribbleStealArm() end return end -- Merely approaching a carrier must never steal. Only a previously observed -- dribble animation can arm this exact possession. if dribbleArmedCarrier ~= carrier then return end if os.clock() < nextStealAt then return end local character = player.Character local carrierCharacter = carrier.Character local root = character and character:FindFirstChild("HumanoidRootPart") local carrierRoot = carrierCharacter and carrierCharacter:FindFirstChild("HumanoidRootPart") local humanoid = character and character:FindFirstChildOfClass("Humanoid") if not root or not carrierRoot or not humanoid or humanoid.Health <= 0 or humanoid.FloorMaterial == Enum.Material.Air then return end local ragdoll = character:FindFirstChild("IsRagdoll") if ragdoll and ragdoll.Value then return end local gameValues = ReplicatedStorage:FindFirstChild("GameValues") local scoring = gameValues and gameValues:FindFirstChild("Scoring") if scoring and scoring.Value then return end local offset = Vector3.new(carrierRoot.Position.X - root.Position.X, 0, carrierRoot.Position.Z - root.Position.Z) local distance = offset.Magnitude if distance < 0.05 or distance > configuredStealRange then -- The end of this dribble was not a valid nearby opportunity. A later -- approach cannot consume it; another real dribble must arm a new attempt. if os.clock() >= dribbleFallbackUntil then clearDribbleStealArm() end return end -- First checkpoint: do not commit yet. An accepted combo animation arriving -- in the next 3 ms re-arms all three deadlines before this reaches 193 ms. if os.clock() < dribbleFallbackUntil then return end if os.clock() < dribbleConfirmUntil then return end if os.clock() > dribbleReleaseDeadline then clearDribbleStealArm() return end pcall(function() defenseController:Steal() end) local values = networkController and networkController.CharValues local nativeStarted = values and values.Stealing == true or movementController and movementController.States and movementController.States.Stealing == true if nativeStarted then clearDribbleStealArm() nextStealAt = os.clock() + currentStealCooldown() else -- Network jitter can move the server boundary by a frame. Retry only inside -- this tiny post-window period, never indefinitely. nextStealAt = os.clock() + 0.03 end end local function legitimateBlockRange() local multiplier = 1 local values = networkController and networkController.CharValues if values then local zone = player:FindFirstChild("Zone") if values.InZone and zone and Zones and Zones.BlockDistBuffZones then multiplier += Zones.BlockDistBuffZones[zone.Value] or 0 end if type(values.MalevolentVisionMode) == "number" and values.MalevolentVisionMode >= workspace:GetServerTimeNow() then multiplier += 0.7 end if type(values.BlockingRangeBoost) == "number" and values.BlockingRangeBoost > 0 then multiplier *= values.BlockingRangeBoost end end pcall(function() for _, emote in networkController.Player.Customization.Equipped.Emotes do if emote == "Emr4n and Luck's Car" then multiplier += 0.25 break end end end) return 25 * multiplier end local function enemyScoringHoop(shooter) if not shooter or not shooter.Team or shooter.Team.Name == "Visitor" then return nil end local hoops = workspace:FindFirstChild("Hoops") local folder = hoops and hoops:FindFirstChild(shooter.Team.Name) local hoop = folder and folder:FindFirstChild("Hoop") return hoop and hoop:IsA("BasePart") and hoop or nil end local function shotIsHeadingToBasket(shooter, ball) local velocity = ball.AssemblyLinearVelocity if velocity.Magnitude < 32 or velocity.Y < 14 then return false end local hoop = enemyScoringHoop(shooter) if not hoop then return false end local flatVelocity = Vector3.new(velocity.X, 0, velocity.Z) local flatTarget = Vector3.new(hoop.Position.X - ball.Position.X, 0, hoop.Position.Z - ball.Position.Z) if flatVelocity.Magnitude < 0.01 or flatTarget.Magnitude < 0.01 then return false end local travel, toward = flatVelocity.Unit, flatTarget.Unit return travel:Dot(toward) > 0.45 end local function canAutoBlock(shooter, ball, preRelease) if not autoBlockEnabled or stopped or os.clock() - lastBlockAt < 0.55 then return false end if not shooter or shooter == player or not shooter.Team or not player.Team or shooter.Team == player.Team or player.Team.Name == "Visitor" then return false end local character = player.Character local shooterCharacter = shooter.Character local root = character and character:FindFirstChild("HumanoidRootPart") local shooterRoot = shooterCharacter and shooterCharacter:FindFirstChild("HumanoidRootPart") local humanoid = character and character:FindFirstChildOfClass("Humanoid") if not root or not shooterRoot or not humanoid or humanoid.Health <= 0 or humanoid.FloorMaterial == Enum.Material.Air or localPossessesBall() then return false end local ragdoll = character:FindFirstChild("IsRagdoll") if not ragdoll or ragdoll.Value then return false end local range = math.min(configuredBlockRange, legitimateBlockRange()) local ballPosition = preRelease and shooterRoot.Position or ball.Position return (ballPosition - root.Position).Magnitude <= range and (shooterRoot.Position - root.Position).Magnitude <= range + 6 end local function jumpImmediately(humanoid) local root = humanoid.Parent and humanoid.Parent:FindFirstChild("HumanoidRootPart") humanoid.Jump = true pcall(humanoid.ChangeState, humanoid, Enum.HumanoidStateType.Jumping) -- ChangeState normally applies the upward velocity on the next physics step. -- Apply that same natural jump velocity now so a replicated pass cue does not -- lose one additional frame locally. if root and root:IsA("BasePart") then pcall(function() local jumpVelocity = humanoid.UseJumpPower and humanoid.JumpPower or math.sqrt(2 * workspace.Gravity * humanoid.JumpHeight) local velocity = root.AssemblyLinearVelocity if velocity.Y < jumpVelocity then root.AssemblyLinearVelocity = Vector3.new(velocity.X, jumpVelocity, velocity.Z) end end) end end local function triggerAutoBlock(shooter, ball) if (pumpSuppressedUntil[shooter] or 0) > os.clock() then return false end if (unresolvedWindupUntil[shooter] or 0) > os.clock() then return false end if not canAutoBlock(shooter, ball) or not shotIsHeadingToBasket(shooter, ball) then return false end local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid") if not humanoid then return false end lastBlockAt = os.clock() lastBlockTarget = shooter jumpImmediately(humanoid) return true end local function inspectEnemyRelease(shooter) if not autoBlockEnabled or blockFlightHandled or not shooter or shooter == player then return end blockDetectionToken += 1 local token = blockDetectionToken task.spawn(function() local deadline = os.clock() + 1.20 while autoBlockEnabled and not stopped and token == blockDetectionToken and os.clock() < deadline do local ball = basketballValue.Value if ball and ball:IsA("BasePart") and ball.Parent and triggerAutoBlock(shooter, ball) then blockFlightHandled = true return end RunService.Heartbeat:Wait() end end) end local function triggerPreReleaseBlock(shooter, confirmedPass) if not confirmedPass and (pumpSuppressedUntil[shooter] or 0) > os.clock() then return false end local ball = basketballValue.Value if not ball or not ball:IsA("BasePart") or not ball.Parent or not canAutoBlock(shooter, ball, true) then return false end local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid") if not humanoid then return false end lastBlockAt = os.clock() lastBlockTarget = shooter blockFlightHandled = true jumpImmediately(humanoid) return true end local animationKind local function inspectEnemyPass(passer, fromAnimation) if not autoBlockEnabled or not autoBlockPassesEnabled or stopped or not passer or passer == player then return end local now = os.clock() local confirmedAfterPump = fromAnimation and (pumpSuppressedUntil[passer] or 0) > now if blockFlightHandled and not fromAnimation then return end -- Pass blocking is intentionally passer-proximity only. Receiver proximity -- and the later ball flight are not considered. if fromAnimation then pumpSuppressedUntil[passer] = 0 unresolvedWindupUntil[passer] = 0 confirmedPumpAt[passer] = 0 end -- A confirmed pass immediately after PumpFake must not inherit a cooldown -- created during the fake's small replication window. if now - lastBlockAt < 0.55 and not confirmedAfterPump then return end if fromAnimation then blockFlightHandled = false end triggerPreReleaseBlock(passer, fromAnimation) end local function tryPostPumpPassCue(owner, animator, pumpAt) if stopped or not autoBlockEnabled or not autoBlockPassesEnabled or confirmedPumpAt[owner] ~= pumpAt or getBallPlayer(false) ~= owner then return end local okTracks, activeTracks = pcall(animator.GetPlayingAnimationTracks, animator) if not okTracks then return end for _, activeTrack in activeTracks do local activeKind = animationKind(activeTrack) if activeKind == "shot" or activeKind == "pump" then return end end inspectEnemyPass(owner, true) end -- Normal shots and pump fakes share the same opening animation. The game -- replaces ShootF/L/R/B with PumpFake at 0.115 seconds only for a fake, so a -- short confirmation after that branch is the earliest reliable block cue. local SHOT_ANIMATION_NAMES = { ShootF = true, ShootL = true, ShootR = true, ShootB = true, FloaterL = true, FloaterR = true } local PUMP_ANIMATION_NAMES = { PumpFake = true, PumpFakeIdle = true } local PASS_ANIMATION_NAMES = { StraightPass1 = true, StraightPass2 = true, Pass1R = true, Pass1L = true, Pass2R = true, Pass2L = true, JumpPassF = true, JumpPassR = true, JumpPassL = true } local STEAL_ANIMATION_NAMES = { StealL = true, StealR = true } local shotAnimationIds = {} local pumpAnimationIds = {} local passAnimationIds = {} local stealAnimationIds = {} local dribbleAnimationIds = {} local dribbleAnimationNames = {} local function animationIdDigits(id) return type(id) == "string" and id:match("%d+") or nil end do local assets = ReplicatedStorage:FindFirstChild("Assets") if assets then for _, object in assets:GetDescendants() do if object:IsA("Animation") then local id = animationIdDigits(object.AnimationId) local dribbleAsset = false local ancestor = object.Parent while ancestor and ancestor ~= assets do if ancestor.Name == "Dribbles" or ancestor.Name:match("Combo$") then dribbleAsset = true break end ancestor = ancestor.Parent end if id and SHOT_ANIMATION_NAMES[object.Name] then shotAnimationIds[id] = object.Name elseif id and PUMP_ANIMATION_NAMES[object.Name] then pumpAnimationIds[id] = object.Name elseif id and PASS_ANIMATION_NAMES[object.Name] then passAnimationIds[id] = object.Name elseif id and STEAL_ANIMATION_NAMES[object.Name] then stealAnimationIds[id] = true elseif id and dribbleAsset then dribbleAnimationIds[id] = true dribbleAnimationNames[object.Name] = true end end end end end animationKind = function(track) if SHOT_ANIMATION_NAMES[track.Name] then return "shot" end if PASS_ANIMATION_NAMES[track.Name] then return "pass" end if STEAL_ANIMATION_NAMES[track.Name] then return "steal" end if track.Name == "PumpFake" then return "pump" end if track.Name == "PumpFakeIdle" then return "idle" end if dribbleAnimationNames[track.Name] then return "dribble" end local ok, animation = pcall(function() return track.Animation end) local id = ok and animation and animationIdDigits(animation.AnimationId) if id and shotAnimationIds[id] then return "shot" end if id and passAnimationIds[id] then return "pass" end if id and stealAnimationIds[id] then return "steal" end if id and pumpAnimationIds[id] == "PumpFake" then return "pump" end if id and pumpAnimationIds[id] == "PumpFakeIdle" then return "idle" end if id and dribbleAnimationIds[id] then return "dribble" end return nil end local observedDribbleTracks = setmetatable({}, { __mode = "k" }) detectCarrierDribbleTrack = function(owner) local character = owner and owner.Character if not character then return false end local animators = {} local humanoid = character:FindFirstChildOfClass("Humanoid") local characterAnimator = humanoid and humanoid:FindFirstChildOfClass("Animator") if characterAnimator then table.insert(animators, characterAnimator) end local plrBall = character:FindFirstChild("PlrBall") local anims = plrBall and plrBall:FindFirstChild("Anims") local animationController = anims and anims:FindFirstChild("AnimationController") local ballAnimator = animationController and animationController:FindFirstChildOfClass("Animator") if ballAnimator then table.insert(animators, ballAnimator) end for _, animator in animators do local ok, tracks = pcall(animator.GetPlayingAnimationTracks, animator) if ok then for _, track in tracks do if not observedDribbleTracks[track] and animationKind(track) == "dribble" then observedDribbleTracks[track] = true armStealAfterEnemyDribble(owner) return true end end end end return false end local animationStates = setmetatable({}, { __mode = "k" }) local watchedCharacters = setmetatable({}, { __mode = "k" }) local function watchEnemyAnimations(owner, character) if owner == player or watchedCharacters[character] then return end watchedCharacters[character] = true task.spawn(function() local humanoid = character:WaitForChild("Humanoid", 8) local animator = humanoid and humanoid:WaitForChild("Animator", 8) if stopped or not animator then return end local state = { serial = 0 } animationStates[owner] = state connect(animator.AnimationPlayed, function(track) if stopped or (not autoBlockEnabled and not autoStealEnabled and not autoDribbleEnabled) or not owner.Team or not player.Team or owner.Team == player.Team or owner.Team.Name == "Visitor" then return end local kind = animationKind(track) if not kind then return end if kind == "steal" then reactToEnemySteal(character) return end if kind == "dribble" then observedDribbleTracks[track] = true armStealAfterEnemyDribble(owner) return end if not autoBlockEnabled then return end if kind == "idle" then -- PassController stops all of the post-pump tracks before passing. -- AnimationController then restarts PumpFakeIdle about 70 ms before -- the possession handoff. A second shot also restarts it, but its new -- Shoot/PumpFake track is already active and is rejected immediately. local pumpAt = confirmedPumpAt[owner] or 0 if autoBlockPassesEnabled and pumpAt > 0 then tryPostPumpPassCue(owner, animator, pumpAt) end return end state.serial += 1 local serial = state.serial if kind == "pump" then -- This is the game's definitive fake cancellation signal. Suppress -- looser possession/velocity fallbacks for this specific actor. local now = os.clock() confirmedPumpAt[owner] = now pumpSuppressedUntil[owner] = now + 0.70 unresolvedWindupUntil[owner] = 0 blockDetectionToken += 1 -- A pass calls AnimationController:StopAll(), cutting PumpFake off -- before its natural end. Defer only until the current synchronous -- action finishes so a replacement Shoot track can veto the guess. local stoppedConnection stoppedConnection = track.Stopped:Connect(function() if stoppedConnection then stoppedConnection:Disconnect() end local okTiming, timePosition, length = pcall(function() return track.TimePosition, track.Length end) if not okTiming or length <= 0 or timePosition >= length - 0.02 then return end task.defer(tryPostPumpPassCue, owner, animator, now) end) table.insert(connections, stoppedConnection) -- If a fallback jumped in the tiny replication window before PumpFake -- arrived, do not let that stale cooldown suppress the following pass. if lastBlockTarget == owner and os.clock() - lastBlockAt < 0.35 then lastBlockAt = -math.huge blockFlightHandled = false end return end -- Ignore unrelated distant animations and animation previews. While the -- track begins, the actor should still be the carrier or beside the ball. local ball = basketballValue.Value local currentCarrier = getBallPlayer(false) local shooterRoot = character:FindFirstChild("HumanoidRootPart") if not ball or not shooterRoot or (currentCarrier ~= owner and (ball.Position - shooterRoot.Position).Magnitude > 12) then return end -- A valid new shooting wind-up starts a fresh flight. This also prevents -- the previous shot's handled flag from suppressing a rapid next attempt. blockFlightHandled = false if kind == "pass" then unresolvedWindupUntil[owner] = 0 if autoBlockPassesEnabled then inspectEnemyPass(owner, true) end return end -- A new real shooting attempt after a fake is independent of that fake. pumpSuppressedUntil[owner] = 0 confirmedPumpAt[owner] = 0 -- Until the game's 0.115-second branch resolves, velocity and possession -- are deliberately forbidden from guessing whether this is a fake. unresolvedWindupUntil[owner] = os.clock() + 0.22 -- The script cannot decide sooner: the shooter's own client does not -- convert the shared wind-up into PumpFake until exactly 0.115 seconds. task.delay(0.20, function() if stopped or not autoBlockEnabled or state.serial ~= serial or blockFlightHandled then return end local okPlaying, playing = pcall(function() return track.IsPlaying end) if not okPlaying or not playing then return end -- Defensively scan active tracks too. This catches a PumpFake event -- delivered on the same frame as the confirmation callback. local okTracks, activeTracks = pcall(animator.GetPlayingAnimationTracks, animator) if okTracks then for _, activeTrack in activeTracks do if animationKind(activeTrack) == "pump" then return end end end -- A steal/pass during the confirmation window cancels it. -- nil is allowed here because a very fast genuine shot may just have released. local confirmedCarrier = getBallPlayer(false) if confirmedCarrier and confirmedCarrier ~= owner then return end triggerPreReleaseBlock(owner) end) end) end) end local function watchEnemyPlayer(owner) if owner == player then return end if owner.Character then watchEnemyAnimations(owner, owner.Character) end connect(owner.CharacterAdded, function(character) watchEnemyAnimations(owner, character) end) end for _, owner in Players:GetPlayers() do watchEnemyPlayer(owner) end connect(Players.PlayerAdded, watchEnemyPlayer) local function scoringHoop() local team = player.Team if not team or team.Name == "Visitor" then return nil end local hoops = workspace:FindFirstChild("Hoops") local teamHoop = hoops and hoops:FindFirstChild(team.Name) local hoop = teamHoop and teamHoop:FindFirstChild("Hoop") return hoop and hoop:IsA("BasePart") and hoop or nil end -- The visible/aiming Hoop is not the actual scoring volume. NewGoalCount is -- the narrow channel immediately below the rim; GoalCount is the older, -- broader fallback lower down. Passing through NewGoalCount's centre also -- leaves the ball comfortably inside GoalCount's footprint. local function scoringChannel(hoop) local channel = hoop and hoop.Parent and hoop.Parent:FindFirstChild("NewGoalCount") return channel and channel:IsA("BasePart") and channel or hoop end local function horizontalUnit(vector) local flat = Vector3.new(vector.X, 0, vector.Z) return flat.Magnitude > 0.001 and flat.Unit or nil end local function isAimingAtScoringHoop(hoop) local camera = workspace.CurrentCamera if not camera then return false end local towardHoop = horizontalUnit(hoop.Position - camera.CFrame.Position) local cameraDirection = horizontalUnit(camera.CFrame.LookVector) if not towardHoop or not cameraDirection then return false end return cameraDirection:Dot(towardHoop) >= math.cos(math.rad(AIM_CONE_DEGREES)) end local function ballIsTravellingTowardHoop(ball, hoop) local velocity = horizontalUnit(ball.AssemblyLinearVelocity) local towardHoop = horizontalUnit(hoop.Position - ball.Position) return velocity ~= nil and towardHoop ~= nil and velocity:Dot(towardHoop) > 0.55 end local function applyNaturalArc(ball, hoop) local channel = scoringChannel(hoop) local destination = channel.Position - Vector3.new(0, 0.12, 0) local displacement = destination - ball.Position local distance = displacement.Magnitude local flightTime if distance >= 115 then -- The game's own FullCourt ability uses this taller equation. The guarded -- final passage below prevents its faster descent skipping the detector. flightTime = math.log(3.5 + distance * 0.055) else flightTime = math.log(1.75 + distance * 0.045) end if flightTime <= 0 then return false end -- Initial velocity under normal workspace gravity. The final-approach guard -- below preserves vertical speed and only removes small horizontal drift. ball.AssemblyLinearVelocity = displacement / flightTime + Vector3.new(0, workspace.Gravity * flightTime * 0.5, 0) return true end -- Normal client prediction can drift slightly over a long flight. Once an -- assisted shot is already descending through the rim area, continuously -- remove only horizontal drift. At the final instant, make a tiny two-frame -- downward passage through the thin scoring slab so a fast ball cannot tunnel -- across it between server physics samples. local function guideFinalApproach(ball, hoop, token) task.spawn(function() local deadline = os.clock() + 7 local possessionGrace = os.clock() + 0.35 while enabled and not stopped and token == shotToken and os.clock() < deadline do if not ball.Parent or basketballValue.Value ~= ball then return end if localPossessesBall() then if os.clock() > possessionGrace then return end task.wait() continue end if not localOwnsBall(ball) then task.wait() continue end local channel = scoringChannel(hoop) if not channel or not channel.Parent then return end local position = ball.Position local velocity = ball.AssemblyLinearVelocity local height = position.Y - channel.Position.Y local horizontalError = (Vector3.new(position.X, 0, position.Z) - Vector3.new(channel.Position.X, 0, channel.Position.Z)).Magnitude if height < -2 then return end if velocity.Y < -1 and height > 0.25 and height <= FINAL_APPROACH_HEIGHT and horizontalError <= FINAL_APPROACH_RADIUS then -- Solve the current vertical motion for the time at which the ball -- reaches the scoring slab, then make X/Z arrive at its centre too. -- Re-evaluating each frame compensates for replication drift without -- altering the shot's vertical arc. local gravity = workspace.Gravity local discriminant = velocity.Y * velocity.Y + 2 * gravity * height local timeToChannel = (velocity.Y + math.sqrt(math.max(0, discriminant))) / gravity if timeToChannel > 0.025 and timeToChannel < 0.65 then local target = channel.Position - Vector3.new(0, 0.12, 0) local horizontal = Vector3.new(target.X - position.X, 0, target.Z - position.Z) / timeToChannel ball.AssemblyLinearVelocity = Vector3.new(horizontal.X, velocity.Y, horizontal.Z) end end -- Far shots can move several studs per physics sample. Once the ball -- is already on the qualified scoring line, explicitly show the server -- a short downward crossing instead of allowing it to skip the slab. if velocity.Y < -1 and height <= 2.4 and height > -0.8 and horizontalError <= 3.5 then local rotation = ball.CFrame.Rotation for _, yOffset in { 0.18, -0.18 } do if stopped or token ~= shotToken or not ball.Parent or basketballValue.Value ~= ball or not localOwnsBall(ball) then return end ball.CFrame = CFrame.new(channel.Position + Vector3.new(0, yOffset, 0)) * rotation ball.AssemblyLinearVelocity = Vector3.new(0, -18, 0) RunService.Heartbeat:Wait() end -- Leave the upper channel on a natural downward path through the -- game's larger legacy GoalCount detector as well. local lower = hoop.Parent:FindFirstChild("GoalCount") if lower and lower:IsA("BasePart") and ball.Parent and localOwnsBall(ball) then local start = ball.Position local fall = math.max(start.Y - lower.Position.Y, 0.1) local exitY = -28 local exitTime = (exitY + math.sqrt(exitY * exitY + 2 * workspace.Gravity * fall)) / workspace.Gravity local horizontal = Vector3.new(lower.Position.X - start.X, 0, lower.Position.Z - start.Z) / math.max(exitTime, 0.05) ball.AssemblyLinearVelocity = Vector3.new(horizontal.X, exitY, horizontal.Z) end return end task.wait() end end) end local function armShot() if not enabled or stopped then return end local hoop = scoringHoop() if not hoop then return end -- Capture intent before the game's aim assist turns the character. If the -- scoring basket is not actually near the camera crosshair, leave the shot -- fully natural—including shots aimed at the defensive basket or a wall. if not isAimingAtScoringHoop(hoop) then return end shotToken += 1 local token = shotToken qualifiedToken = token shotArmedUntil = os.clock() + 1.6 task.spawn(function() local deadline = os.clock() + 1.4 while enabled and not stopped and token == shotToken and os.clock() < deadline do -- Prefer the BallService.Throw acknowledgment below. This delayed path -- is only a fallback if that service signal is unavailable. if ballService and os.clock() < deadline - 0.75 then task.wait() continue end local ball = basketballValue.Value if ball and ball:IsA("BasePart") and ball.Parent and localOwnsBall(ball) and not localPossessesBall() and ball.AssemblyLinearVelocity.Magnitude > 6 then -- Resolve again in case the player's team changed during the wind-up. local currentHoop = scoringHoop() if currentHoop and qualifiedToken == token and appliedToken ~= token and ballIsTravellingTowardHoop(ball, currentHoop) and applyNaturalArc(ball, currentHoop) then appliedToken = token guideFinalApproach(ball, currentHoop, token) return end end task.wait() end end) end -- The possession remote reaches every client and is the reliable defender-side -- signal. It exposes both ordinary carrier>nil releases and the carrier>teammate -- direct handoff used by some short/post-pump passes. if ballState and type(ballState.onPlayerPossessingBallChangedConnect) == "function" then local previousPossessor = getBallPlayer(false) local ok, disconnect = pcall(ballState.onPlayerPossessingBallChangedConnect, ballState, function(currentPossessor) local releasedBy = previousPossessor previousPossessor = currentPossessor if releasedBy and currentPossessor and currentPossessor ~= releasedBy and releasedBy.Team and currentPossessor.Team and releasedBy.Team == currentPossessor.Team then -- Short/fast passes can replicate as passer>receiver directly, with -- neither a nil possession gap nor a visible pass animation. inspectEnemyPass(releasedBy, true) elseif releasedBy and currentPossessor == nil then inspectEnemyRelease(releasedBy) confirmedPumpAt[releasedBy] = 0 pumpSuppressedUntil[releasedBy] = 0 unresolvedWindupUntil[releasedBy] = 0 elseif releasedBy and currentPossessor ~= releasedBy then -- A steal or other cross-team transfer ends the old carrier's pump. confirmedPumpAt[releasedBy] = 0 pumpSuppressedUntil[releasedBy] = 0 unresolvedWindupUntil[releasedBy] = 0 end end, false) if ok and type(disconnect) == "function" then table.insert(cleanupCallbacks, disconnect) end end -- Continuous fallback: defender replication can deliver the velocity well -- after the release event. Polling the already-replicated state removes that -- race without firing or hooking any remotes. do local observedPossessor = getBallPlayer(false) local lastCarrier = observedPossessor connect(RunService.Heartbeat, function() local currentPossessor = getBallPlayer(false) if currentPossessor then if currentPossessor ~= observedPossessor then blockFlightHandled = false end observedPossessor = currentPossessor lastCarrier = currentPossessor return end if observedPossessor then lastCarrier = observedPossessor observedPossessor = nil end if not autoBlockEnabled or blockFlightHandled or stopped then return end local ball = basketballValue.Value if not ball or not ball:IsA("BasePart") or not ball.Parent then return end -- Only an actual carrier may be treated as the shooter. A nearest-player -- guess can become the receiver during a pass and is intentionally avoided. local shooter = getBallPlayer(true) or lastCarrier if shooter and triggerAutoBlock(shooter, ball) then blockFlightHandled = true end end) end -- Check every physics frame so the attempt lands immediately after the game's -- real dribble window plus the user's adjustable safety delay. connect(RunService.Heartbeat, tryAutoSteal) -- The server first acknowledges a valid shot by firing BallService.Throw with -- its chosen landing point. The native client then writes that trajectory. -- Since this connection is added later, waiting one Heartbeat guarantees our -- one-time natural arc is applied after that native write instead of before it. if ballService and ballService.Throw then connect(ballService.Throw, function(serverTarget) inspectEnemyRelease(getBallPlayer(true)) if not enabled or stopped or serverTarget == nil or os.clock() > shotArmedUntil then return end local token = shotToken task.spawn(function() RunService.Heartbeat:Wait() if not enabled or stopped or token ~= shotToken or qualifiedToken ~= token or appliedToken == token then return end local ball = basketballValue.Value local hoop = scoringHoop() if ball and ball:IsA("BasePart") and ball.Parent and hoop and localOwnsBall(ball) and not localPossessesBall() and ballIsTravellingTowardHoop(ball, hoop) and applyNaturalArc(ball, hoop) then appliedToken = token guideFinalApproach(ball, hoop, token) end end) end) end -- Pass is replicated to every client before the pass physics settle. The -- game's last-possession record identifies the passer; the current/nearest -- player may already be the receiver and must never be substituted. if ballService and ballService.Pass then connect(ballService.Pass, function() if not autoBlockPassesEnabled then return end local passer = getBallPlayer(true) inspectEnemyPass(passer, true) end) end -- Unlike animation guessing, this is the same replicated cue the game's own -- DefenseController uses to animate an enemy's committed steal lunge. if ballService and ballService.Steal then connect(ballService.Steal, function(thiefCharacter, targetCFrame) reactToEnemySteal(thiefCharacter, targetCFrame) end) end local function stop() if stopped then return end stopped = true enabled = false autoBlockEnabled = false autoStealEnabled = false autoDribbleEnabled = false releaseAutoDribbleKey() shotToken += 1 blockDetectionToken += 1 for _, connection in ipairs(connections) do pcall(function() connection:Disconnect() end) end for _, callback in ipairs(cleanupCallbacks) do pcall(callback) end if Rayfield then pcall(function() Rayfield:Destroy() end) end sharedEnvironment.__BasketNaturalAim = nil end sharedEnvironment.__BasketNaturalAim = { Stop = stop } connect(UserInputService.InputBegan, function(input, processed) if processed or not isShootInput(input) then return end armShot() end) local shotToggle local blockToggle local passToggle local stealToggle local dribbleToggle local function setShotAssist(value, announce) enabled = value == true shotToken += 1 if announce then notify(enabled and "Perfect Shot enabled" or "Perfect Shot disabled — shots are untouched") end end local function setAutoBlock(value, announce) autoBlockEnabled = value == true blockDetectionToken += 1 blockFlightHandled = false if announce then notify(autoBlockEnabled and "Auto Block enabled" or "Auto Block disabled") end end local function setAutoSteal(value, announce) autoStealEnabled = value == true nextStealAt = 0 clearDribbleStealArm() if announce then notify(autoStealEnabled and "Auto Steal enabled" or "Auto Steal disabled") end end local function setAutoDribble(value, announce) autoDribbleEnabled = value == true lastAutoDribbleAt = 0 if not autoDribbleEnabled then releaseAutoDribbleKey() end if announce then notify(autoDribbleEnabled and "Auto Dribble enabled" or "Auto Dribble disabled") end end local rayfieldOk, rayfieldResult = pcall(function() return loadstring(game:HttpGet("https://sirius.menu/rayfield"))() end) if rayfieldOk and rayfieldResult then Rayfield = rayfieldResult local Window = Rayfield:CreateWindow({ Name = "Basket Assist", Icon = "dribbble", LoadingTitle = "Basket Assist", LoadingSubtitle = "Natural shots + reactive defense", ShowText = "Basket", Theme = "Default", -- Mouse clicks use KeyCode.Unknown. A harmless keyboard-only placeholder -- prevents Rayfield's native window toggle from reacting to every click. ToggleUIKeybind = Enum.KeyCode.World95, DisableRayfieldPrompts = false, DisableBuildWarnings = false, ConfigurationSaving = { Enabled = true, FolderName = "BasketAssist", FileName = "Settings" }, Discord = { Enabled = false }, KeySystem = false }) local ShotTab = Window:CreateTab("Perfect Shot", "crosshair") ShotTab:CreateSection("Automatic Swish") shotToggle = ShotTab:CreateToggle({ Name = "Perfect Shot Assist", CurrentValue = true, Flag = "PerfectShotEnabled", Callback = function(value) setShotAssist(value, true) end }) ShotTab:CreateKeybind({ Name = "Toggle Perfect Shot", CurrentKeybind = "P", HoldToInteract = false, Flag = "PerfectShotKeybind", Callback = function() setShotAssist(not enabled, true) if shotToggle then pcall(function() shotToggle:Set(enabled) end) end end }) ShotTab:CreateLabel("Only assists when you genuinely aim toward the enemy basket.", "target") ShotTab:CreateLabel("Shots toward your own net or elsewhere remain untouched.", "shield-check") local DefenseTab = Window:CreateTab("Auto Block", "shield") DefenseTab:CreateSection("Reactive Defense") blockToggle = DefenseTab:CreateToggle({ Name = "Auto Block Enemy Shots", CurrentValue = true, Flag = "AutoBlockEnabled", Callback = function(value) setAutoBlock(value, true) end }) passToggle = DefenseTab:CreateToggle({ Name = "Auto Block Enemy Passes", CurrentValue = true, Flag = "AutoBlockPassesEnabled", Callback = function(value) autoBlockPassesEnabled = value == true notify(autoBlockPassesEnabled and "Pass blocking enabled" or "Pass blocking disabled") end }) DefenseTab:CreateKeybind({ Name = "Toggle Auto Block", CurrentKeybind = "B", HoldToInteract = false, Flag = "AutoBlockKeybind", Callback = function() setAutoBlock(not autoBlockEnabled, true) if blockToggle then pcall(function() blockToggle:Set(autoBlockEnabled) end) end end }) DefenseTab:CreateSlider({ Name = "Maximum Reaction Range", Range = { 5, 50 }, Increment = 1, Suffix = " studs", CurrentValue = 25, Flag = "AutoBlockRange", Callback = function(value) configuredBlockRange = tonumber(value) or 25 end }) DefenseTab:CreateLabel("The game still enforces its real 25-stud range and legitimate boosts.", "ruler") DefenseTab:CreateLabel("Waits for the real shot decision; PumpFake and PumpFakeIdle never trigger a jump.", "shield-check") DefenseTab:CreateLabel("Passes trigger only when you are close to the passer, never merely the receiver.", "activity") local StealTab = Window:CreateTab("Auto Steal", "hand") StealTab:CreateSection("Protected Timing") stealToggle = StealTab:CreateToggle({ Name = "Auto Steal Ball Carriers", CurrentValue = true, Flag = "AutoStealEnabled", Callback = function(value) setAutoSteal(value, true) end }) StealTab:CreateKeybind({ Name = "Toggle Auto Steal", CurrentKeybind = "N", HoldToInteract = false, Flag = "AutoStealKeybind", Callback = function() setAutoSteal(not autoStealEnabled, true) if stealToggle then pcall(function() stealToggle:Set(autoStealEnabled) end) end end }) StealTab:CreateSlider({ Name = "Activation Range", Range = { 6, 18 }, Increment = 1, Suffix = " studs", CurrentValue = 16, Flag = "AutoStealRange", Callback = function(value) configuredStealRange = tonumber(value) or 16 end }) StealTab:CreateInput({ Name = "First Steal Check (ms)", CurrentValue = "190", PlaceholderText = "Enter 0-500", RemoveTextAfterFocusLost = false, Flag = "AutoStealFirstCheckMs", Callback = function(value) local milliseconds = tonumber(value) if milliseconds then configuredStealWaitMs = math.clamp(math.round(milliseconds), 0, 500) end end }) StealTab:CreateLabel("Only a real enemy dribble arms one steal attempt.", "scan") StealTab:CreateLabel("Each accepted chained dribble resets the timer; rejected Q spam is ignored.", "shield-check") StealTab:CreateLabel("First check: typed value. Final check: exactly 3 ms later.", "activity") StealTab:CreateLabel("Default 190 ms → final steal checkpoint at 193 ms.", "timer") local DribbleTab = Window:CreateTab("Auto Dribble", "shield-check") DribbleTab:CreateSection("Steal Protection") dribbleToggle = DribbleTab:CreateToggle({ Name = "Dribble Against Enemy Steals", CurrentValue = true, Flag = "AutoDribbleEnabled", Callback = function(value) setAutoDribble(value, true) end }) DribbleTab:CreateKeybind({ Name = "Toggle Auto Dribble", CurrentKeybind = "M", HoldToInteract = false, Flag = "AutoDribbleKeybind", Callback = function() setAutoDribble(not autoDribbleEnabled, true) if dribbleToggle then pcall(function() dribbleToggle:Set(autoDribbleEnabled) end) end end }) DribbleTab:CreateSlider({ Name = "Confirmed Steal Range", Range = { 8, 26 }, Increment = 1, Suffix = " studs", CurrentValue = 24, Flag = "AutoDribbleReactionRange", Callback = function(value) configuredAutoDribbleRange = tonumber(value) or 24 end }) DribbleTab:CreateLabel("Triggers only after a confirmed enemy steal event or StealL/StealR animation.", "scan") DribbleTab:CreateLabel("Uses your configured keyboard Dribble bind and never reacts to proximity alone.", "shield-check") local SettingsTab = Window:CreateTab("Settings", "settings") SettingsTab:CreateSection("Interface") SettingsTab:CreateKeybind({ Name = "Show / Hide Window", CurrentKeybind = "RightShift", HoldToInteract = false, Flag = "WindowKeybind", Callback = function() local visible = true pcall(function() visible = Rayfield:IsVisible() end) pcall(function() Rayfield:SetVisibility(not visible) end) end }) SettingsTab:CreateLabel("Only the selected keyboard key can show or hide this window.", "keyboard") pcall(function() Rayfield:LoadConfiguration() end) sharedEnvironment.__BasketNaturalAim.Rayfield = Rayfield Rayfield:Notify({ Title = "Basket Assist ready", Content = "Perfect Shot, Auto Block, Auto Steal, and Auto Dribble are enabled.", Duration = 5, Image = "shield-check" }) else warn("Basket Assist: Rayfield failed to load: " .. tostring(rayfieldResult)) notify("Rayfield failed to load; shot and block assistance remain enabled") end