(always in, auto block, auto steal, ...)
Key Required

(always in, auto block, auto steal, ...)

[⚙️QOL✨] Basketball: ZeroRandomDidiRandomDidi 1.7K views 6.4K executesUpdated Sep 5, 2026
1.3/ 5(4 votes)
1325% Positive
Lua Script•1349 lines (51278 chars)
-- 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

Features & Information

- Aim assist (have to look at hoop) - Always in - Auto steal (can adjust ms) - Auto block (pass *and* shoots) - Auto dribble (buggy, don't trust it too much) - More...

Community Comments (6)

Hu2s3yHu2s3ySep 8, 2026

use this to get instant ban for 3 days

Replying to @Hu2s3y
RandomDidiRandomDidiSep 8, 2026

@Hu2s3y I'm sorry to hear that. It's probably because of the executor. You need one that isn't as easily detected. I'm using Pot*ium, and I haven't had any issues so far.

Margarit099Margarit099Sep 17, 2026

Great script. Didn't have any issues with it

StaticAmourStaticAmourSep 19, 2026

theres really only one thing you need to fix is the lock on its very blatent

Replying to @StaticAmour
StaticAmourStaticAmourSep 19, 2026

nvm the perfect shoots really *ing blatent

StaticAmourStaticAmourOct 10, 2026

THIS **** GOT PATCHED

More Scripts for [⚙️QOL✨] Basketball: Zero