BobloScript.com

Slap Ball script - (Perfect Slap)

Published by bebraOriginal developer: DonnieAzoffPublished Jul 27
Open in Roblox
Community:
56Views
24Copies
NeverUpdated

Description

Perfect Slap Without Manual Timing

This Slap Ball script by DonnieAzoff is a simple open-source tool focused on one function: Perfect Slap. It handles the slap timing for you, which is useful when hitting the ball at the correct moment normally requires precise input.

There are no extra farming or movement tools — the script is made specifically for players who want more consistent slap timing. It is also no key, so there is no separate key-system step before execution.

How to use the script?

  1. Open Slap Ball and wait for the match to load.

  2. Copy the DonnieAzoff script.

  3. Open a compatible Roblox executor.

  4. Paste the code and press Execute.

  5. Play normally and let Perfect Slap handle the supported slap timing.

Because the script is open source, you can inspect the code before running it. Game updates may still change the slap system and break the function.

Functions

  • Perfect Slap

Script Code

Raw Code

This script loads code from an external source. Review the URL and understand what it does before running it in Roblox.

https://raw.githubusercontent.com/deividcomsono/Obsidian/main/

local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local RunService = game:GetService("RunService")local CoreGui = game:GetService("CoreGui")local LocalPlayer = Players.LocalPlayerlocal oldGui = CoreGui:FindFirstChild("SlapBallGui")if oldGui then	pcall(function()		oldGui:Destroy()	end)endlocal Tetherball = ReplicatedStorage:WaitForChild("Tetherball")local Config = require(Tetherball:WaitForChild("Config"))local BallMath = require(Tetherball:WaitForChild("BallMath"))local Remotes = Tetherball:WaitForChild("Remotes")local ClientInput = Remotes:WaitForChild("ClientInput")local MatchEvent = Remotes:WaitForChild("MatchEvent")local Courts = workspace:WaitForChild("TetherballCourts")local TAU = 6.283185307179586local PI = 3.141592653589793local MAX_REWIND = Config.MaxRewind or 0.35local REWIND_SAFE = MAX_REWIND * 0.78local PERFECT_ANGLE = Config.PerfectAngle or 0.10471975511965978local LOCKOUT = Config.MissClickLockout or 0.75local court, side, sideAnglelocal serveArmed = falselocal serveRetry = 0local lastStamp = 0local lockUntil = 0local pending = nillocal scheduled = nillocal gen = 0local clockOffset = 0local latency = 0.06local samples = {}local freeKey, freeListlocal matchHitOk = falselocal badStreak = 0local failStreak = 0local marginIndex = 1local margins = {	{ value = 0.06, ok = 0, fail = 0 },	{ value = -0.03, ok = 0, fail = 0 },	{ value = 0.15, ok = 0, fail = 0 },	{ value = 0, ok = 0, fail = 0 },}local connections = {}local stateConnlocal function track(conn)	table.insert(connections, conn)	return connendlocal function angDiff(a, b)	return math.atan2(math.sin(a - b), math.cos(a - b))endlocal function serverNow()	return workspace:GetServerTimeNow() + clockOffsetendlocal function readState()	if not court or not court.Parent then		return nil	end	return BallMath.decode(court:GetAttribute(BallMath.ATTR))endlocal function stateKey(state)	if not state then		return "nil"	end	return string.format("%s|%.5f|%.5f|%.5f", tostring(state.m), state.t or 0, state.o or 0, state.th or 0)endlocal function noteLatency(state)	if not state or type(state.t) ~= "number" then		return	end	local delta = workspace:GetServerTimeNow() - state.t	if delta ~= delta or math.abs(delta) > 5 then		return	end	table.insert(samples, delta)	if #samples > 24 then		table.remove(samples, 1)	end	local lowest = math.huge	for _, value in samples do		if value < lowest then			lowest = value		end	end	if lowest < 0 then		clockOffset = clockOffset - lowest + 0.002		table.clear(samples)		return	end	latency = math.clamp(lowest, 0.01, 0.4)endlocal function resetMatch()	pending = nil	scheduled = nil	lastStamp = 0	lockUntil = 0	freeKey = nil	freeList = nil	gen = gen + 1endlocal function bindCourt(model, index)	court = model	side = index	sideAngle = index == 1 and 0 or PI	resetMatch()	if stateConn then		stateConn:Disconnect()		stateConn = nil	end	if court then		stateConn = court:GetAttributeChangedSignal(BallMath.ATTR):Connect(function()			noteLatency(readState())			scheduled = nil		end)		track(stateConn)	endendlocal function flipSide()	if not side then		return	end	bindCourt(court, side == 1 and 2 or 1)endlocal function resolveCourt()	local character = LocalPlayer.Character	local root = character and character:FindFirstChild("HumanoidRootPart")	if not root then		return	end	local bestModel, bestIndex, bestDist	for _, model in Courts:GetChildren() do		if model:IsA("Model") then			for index = 1, 2 do				local pad = model:FindFirstChild("PlayerPad" .. index)				if pad then					local dist = (pad.Position - root.Position).Magnitude					if dist <= 18 and (not bestDist or dist < bestDist) then						bestModel, bestIndex, bestDist = model, index, dist					end				end			end		end	end	if bestModel and (bestModel ~= court or bestIndex ~= side) then		bindCourt(bestModel, bestIndex)	endendlocal function rallyCandidates(state, from, out)	local o = state.o	if not o or o == 0 then		return	end	local theta = BallMath.eval(state, from)	local dir = o >= 0 and 1 or -1	local travel = (angDiff(sideAngle, theta) * dir) % TAU	local period = 1 / math.abs(o)	local first = from + (travel / TAU) * period	for index = 0, 3 do		table.insert(out, first + period * index)	endendlocal function freeCandidates(state, from, out)	local key = stateKey(state)	if key ~= freeKey or not freeList then		local found = {}		local step = 1 / 240		local limit = from + 6		local prevT = from		local prev = angDiff(BallMath.eval(state, prevT), sideAngle)		local t = from		while t < limit and #found < 4 do			t = t + step			local cur = angDiff(BallMath.eval(state, t), sideAngle)			if (prev >= 0) ~= (cur >= 0) and math.abs(prev) + math.abs(cur) < 0.9 then				local lo, hi = prevT, t				for _ = 1, 30 do					local mid = (lo + hi) * 0.5					if (angDiff(BallMath.eval(state, mid), sideAngle) >= 0) == (prev >= 0) then						lo = mid					else						hi = mid					end				end				table.insert(found, (lo + hi) * 0.5)			end			prev, prevT = cur, t		end		freeKey = key		freeList = found	end	for _, value in freeList do		table.insert(out, value)	endendlocal function currentMargin()	if failStreak >= 2 then		local bestIndex, bestScore		for index, entry in margins do			if index ~= marginIndex then				local score = (entry.ok + 1) / (entry.ok + entry.fail + 2)				if not bestScore or score > bestScore then					bestIndex, bestScore = index, score				end			end		end		if bestIndex then			marginIndex = bestIndex		end		failStreak = 0	end	return margins[marginIndex]endlocal function sendClick(stamp, marginUsed)	lastStamp = stamp	pending = {		stamp = stamp,		margin = marginUsed,		clock = os.clock(),		tries = 1,	}	ClientInput:FireServer("click", stamp)endlocal function scoreOutcome(perfect)	local entry = pending and pending.margin or margins[marginIndex]	if perfect then		entry.ok = entry.ok + 1		failStreak = 0	else		entry.fail = entry.fail + 1		failStreak = failStreak + 1	endendtrack(MatchEvent.OnClientEvent:Connect(function(name, data)	data = data or {}	if name == "MatchStart" or name == "Queued" then		if data.court and data.side then			bindCourt(data.court, data.side)		end		serveArmed = false		matchHitOk = false		badStreak = 0	elseif name == "ServeReady" then		serveArmed = data.server == LocalPlayer		serveRetry = 0		resetMatch()	elseif name == "Turn" then		serveArmed = false	elseif name == "HitResult" then		if side and data.side == side then			matchHitOk = true			badStreak = 0			local rtt = pending and (os.clock() - pending.clock) or nil			if rtt and rtt > 0 and rtt < 1.5 then				latency = math.clamp(math.min(latency, rtt * 0.5), 0.01, 0.4)			end			scoreOutcome(data.grade == "Perfect")			pending = nil		end	elseif name == "BadClick" then		scoreOutcome(false)		pending = nil		scheduled = nil		lockUntil = serverNow() + LOCKOUT + 0.05		if not matchHitOk then			badStreak = badStreak + 1			if badStreak >= 2 then				badStreak = 0				flipSide()			end		end	endend))local repo = "https://raw.githubusercontent.com/deividcomsono/Obsidian/main/"local Library = loadstring(game:HttpGet(repo .. "Library.lua"))()local SaveManager = loadstring(game:HttpGet(repo .. "addons/SaveManager.lua"))()local ThemeManager = loadstring(game:HttpGet(repo .. "addons/ThemeManager.lua"))()Library.ForceCheckbox = falseLibrary.ShowToggleFrameInKeybinds = truelocal Window = Library:CreateWindow({	Title = "Slap Ball",	Footer = "Slap Ball | Made by DonnieAzoff",	AutoShow = true,	NotifySide = "Right",	ShowCustomCursor = false,})local MainTab = Window:AddTab("Main", "swords")local SlapGroup = MainTab:AddLeftGroupbox("Combat", "zap")SlapGroup:AddToggle("PerfectSlap", {	Text = "PerfectSlap",	Default = false,})local CreditsGroup = MainTab:AddRightGroupbox("Credits", "badge-check")CreditsGroup:AddLabel("Slap Ball")CreditsGroup:AddLabel("Script made by DonnieAzoff")local SettingsTab = Window:AddTab("Settings", "sliders-horizontal")local candidates = {}local function enabled()	local toggle = Library.Toggles.PerfectSlap	return toggle and toggle.Valueendlocal function commit(cross, marginUsed, key)	if not enabled() then		return	end	local state = readState()	if not state or stateKey(state) ~= key then		return	end	local now = serverNow()	local stamp = cross	if now - stamp > REWIND_SAFE then		stamp = now - REWIND_SAFE	end	if math.abs(angDiff(BallMath.eval(state, stamp), sideAngle)) > PERFECT_ANGLE * 0.4 then		return	end	sendClick(stamp, marginUsed)endlocal function step()	if not enabled() then		return	end	if not court or not court.Parent or not sideAngle then		resolveCourt()		if not court or not sideAngle then			return		end	end	local state = readState()	if not state then		return	end	local now = serverNow()	if state.m == "rest" or state.m == "hold" then		pending = nil		scheduled = nil		if serveArmed and now >= serveRetry and now >= lockUntil then			serveRetry = now + 0.55			ClientInput:FireServer("click", now)		end		return	end	if pending then		local waited = os.clock() - pending.clock		if waited > latency * 2 + 0.12 then			if pending.tries < 3 and (now - pending.stamp) + latency < REWIND_SAFE then				pending.tries = pending.tries + 1				pending.clock = os.clock()				ClientInput:FireServer("click", pending.stamp)			else				pending = nil			end		end	end	if now < lockUntil then		return	end	local _, _, speed = BallMath.eval(state, now)	if not speed or speed <= 0 then		return	end	table.clear(candidates)	local from = now - REWIND_SAFE * 0.9	if state.m == "rally" then		rallyCandidates(state, from, candidates)	else		freeCandidates(state, from, candidates)	end	if #candidates == 0 then		return	end	table.sort(candidates)	local target	for _, cross in candidates do		if cross > lastStamp + 0.12 then			if cross >= now or (now - cross) + latency <= REWIND_SAFE then				target = cross				break			end		end	end	if not target then		return	end	if scheduled and math.abs(scheduled - target) < 0.02 then		return	end	local margin = currentMargin()	local lead = math.clamp(latency - margin.value, 0, REWIND_SAFE * 0.85)	local fireAt = target - lead	local key = stateKey(state)	local delay = fireAt - now	if delay <= 0 then		scheduled = target		commit(target, margin, key)		return	end	if delay <= 0.35 then		scheduled = target		local token = gen		task.delay(delay, function()			if token == gen then				commit(target, margin, key)			end		end)	endendlocal function safeStep()	local ok, err = pcall(step)	if not ok then		warn(err)	endendtrack(RunService.RenderStepped:Connect(safeStep))track(RunService.Heartbeat:Connect(safeStep))SaveManager:SetLibrary(Library)SaveManager:IgnoreThemeSettings()SaveManager:SetFolder("Slap Ball")SaveManager:SetIgnoreIndexes({ "MenuKeybind" })ThemeManager:SetLibrary(Library)ThemeManager:SetFolder("Slap Ball")local ConfigGroupbox = SettingsTab:AddLeftGroupbox("Configuration", "folder-cog")ConfigGroupbox:AddInput("SaveManager_ConfigName", {	Text = "Config name",	Placeholder = "My Config",})ConfigGroupbox:AddButton("Create config", function()	local name = Library.Options.SaveManager_ConfigName.Value	if name:gsub(" ", "") == "" then		Library:Notify("Invalid config name (empty)", 2)		return	end	local success, err = SaveManager:Save(name)	if not success then		Library:Notify("Failed to create config: " .. err)		return	end	Library:Notify(string.format("Created config %q", name))	Library.Options.SaveManager_ConfigList:SetValues(SaveManager:RefreshConfigList())	Library.Options.SaveManager_ConfigList:SetValue(nil)end)ConfigGroupbox:AddDivider()ConfigGroupbox:AddDropdown("SaveManager_ConfigList", {	Text = "Config list",	Values = SaveManager:RefreshConfigList(),	AllowNull = true,})ConfigGroupbox:AddButton("Load config", function()	local name = Library.Options.SaveManager_ConfigList.Value	local success, err = SaveManager:Load(name)	if not success then		Library:Notify("Failed to load config: " .. err)		return	end	Library:Notify(string.format("Loaded config %q", name))end)ConfigGroupbox:AddButton("Overwrite config", function()	local name = Library.Options.SaveManager_ConfigList.Value	local success, err = SaveManager:Save(name)	if not success then		Library:Notify("Failed to overwrite config: " .. err)		return	end	Library:Notify(string.format("Overwrote config %q", name))end)ConfigGroupbox:AddButton("Delete config", function()	local name = Library.Options.SaveManager_ConfigList.Value	local success, err = SaveManager:Delete(name)	if not success then		Library:Notify("Failed to delete config: " .. err)		return	end	Library:Notify(string.format("Deleted config %q", name))	Library.Options.SaveManager_ConfigList:SetValues(SaveManager:RefreshConfigList())	Library.Options.SaveManager_ConfigList:SetValue(nil)end)ConfigGroupbox:AddButton("Refresh list", function()	Library.Options.SaveManager_ConfigList:SetValues(SaveManager:RefreshConfigList())	Library.Options.SaveManager_ConfigList:SetValue(nil)end)local AutoloadLabel = ConfigGroupbox:AddLabel("Current autoload config: " .. SaveManager:GetAutoloadConfig(), true)ConfigGroupbox:AddButton("Set as autoload", function()	local name = Library.Options.SaveManager_ConfigList.Value	local success, err = SaveManager:SaveAutoloadConfig(name)	if not success then		Library:Notify("Failed to set autoload config: " .. err)		return	end	Library:Notify(string.format("Set %q to auto load", name))	AutoloadLabel:SetText("Current autoload config: " .. name)end)ConfigGroupbox:AddButton("Reset autoload", function()	local success, err = SaveManager:DeleteAutoLoadConfig()	if not success then		Library:Notify("Failed to reset autoload config: " .. err)		return	end	Library:Notify("Set autoload to none")	AutoloadLabel:SetText("Current autoload config: none")end)local MenuGroup = SettingsTab:AddRightGroupbox("Menu", "wrench")MenuGroup:AddLabel("Menu bind"):AddKeyPicker("MenuKeybind", {	Default = "RightControl",	NoUI = true,	Text = "Menu keybind",})MenuGroup:AddButton("Unload", function()	Library:Unload()end)SaveManager:SetIgnoreIndexes({ "SaveManager_ConfigList", "SaveManager_ConfigName", "MenuKeybind" })Library.ToggleKeybind = Library.Options.MenuKeybindThemeManager:ApplyToTab(SettingsTab)SaveManager:LoadAutoloadConfig()Library:OnUnload(function()	gen = gen + 1	for _, conn in connections do		pcall(function()			conn:Disconnect()		end)	end	table.clear(connections)	table.clear(samples)	stateConn = nil	court = nil	side = nil	sideAngle = nil	serveArmed = false	pending = nil	scheduled = nil	freeKey = nil	freeList = nil	print("Slap Ball by DonnieAzoff unloaded.")end)
Latest changeScript publishedJul 27

Comments

Loading comments…