-- [[ Rscripts Risk Notice ]] -- This script is not verified by rscripts.net. Deal with caution. -- -- Stay safe: -- • Never log in on unofficial Roblox sites or lookalike domains. -- • Real Roblox links use roblox.com (check the .com ending). -- • Treat fake Roblox login / "claim reward" pages as phishing. -- [[ End Rscripts Risk Notice ]] local KEY_LEFT = Enum.KeyCode.A local KEY_RIGHT = Enum.KeyCode.D local KEY_RESET = Enum.KeyCode.E -- clears jump history and averages local TURN_DEADZONE = 0.5 -- rad/s of yaw before a frame counts as "turning" local SPEED_WINDOW = 0.1 -- seconds of position samples used for speed local AIR_MARGIN = 0.3 -- studs above the ground baseline that count as airborne local HISTORY = 10 -- jumps in the rolling average local RAY_LENGTH = 50 local env = getgenv() if env.StrafeTrainer then pcall(env.StrafeTrainer.destroy) end local Players = game:GetService("Players") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") local UserInputService = game:GetService("UserInputService") local LocalPlayer = Players.LocalPlayer local COLOR_IDLE = Color3.fromRGB(58, 58, 66) local COLOR_HELD = Color3.fromRGB(120, 120, 132) local COLOR_HINT = Color3.fromRGB(52, 110, 70) local COLOR_GOOD = Color3.fromRGB(70, 215, 120) local COLOR_BAD = Color3.fromRGB(225, 70, 70) local COLOR_DIM = Color3.fromRGB(150, 150, 162) local TW_BAR = TweenInfo.new(0.12, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) local TW_FADE = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) local TW_SETTLE = TweenInfo.new(0.45, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) local TW_POP = TweenInfo.new(0.3, Enum.EasingStyle.Back, Enum.EasingDirection.Out) local stats = { speed = 0, airborne = false, jumps = 0, lastSync = nil, lastGain = nil, avgSync = nil, avgGain = nil, } local function tween(obj, info, props) local t = TweenService:Create(obj, info, props) t:Play() return t end -- GUI local gui = Instance.new("ScreenGui") gui.Name = "StrafeTrainer" gui.ResetOnSpawn = false gui.IgnoreGuiInset = true local panel = Instance.new("CanvasGroup") panel.AnchorPoint = Vector2.new(0.5, 1) panel.Position = UDim2.new(0.5, 0, 1, -100) panel.Size = UDim2.fromOffset(168, 44) panel.BackgroundColor3 = Color3.fromRGB(12, 12, 15) panel.BackgroundTransparency = 0.45 panel.BorderSizePixel = 0 panel.GroupTransparency = 1 panel.Parent = gui Instance.new("UICorner", panel).CornerRadius = UDim.new(0, 7) local scale = Instance.new("UIScale") scale.Scale = 0.92 scale.Parent = panel local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(255, 255, 255) stroke.Transparency = 0.88 stroke.Thickness = 1 stroke.Parent = panel -- Thin bars flanking the readout: they light up for the key that matches the turn. local function makeBar(anchorX, xScale, xOffset) local bar = Instance.new("Frame") bar.AnchorPoint = Vector2.new(anchorX, 0.5) bar.Position = UDim2.new(xScale, xOffset, 0.5, 0) bar.Size = UDim2.fromOffset(3, 16) bar.BackgroundColor3 = COLOR_IDLE bar.BorderSizePixel = 0 bar.Parent = panel Instance.new("UICorner", bar).CornerRadius = UDim.new(1, 0) return bar end local barLeft = makeBar(0, 0, 9) local barRight = makeBar(1, 1, -9) local speedLabel = Instance.new("TextLabel") speedLabel.AnchorPoint = Vector2.new(0.5, 0) speedLabel.Position = UDim2.new(0.5, 0, 0, 5) speedLabel.Size = UDim2.fromOffset(120, 21) speedLabel.BackgroundTransparency = 1 speedLabel.Font = Enum.Font.GothamBold speedLabel.TextSize = 20 speedLabel.TextColor3 = Color3.new(1, 1, 1) speedLabel.Text = "0.0" speedLabel.Parent = panel local statLabel = Instance.new("TextLabel") statLabel.AnchorPoint = Vector2.new(0.5, 0) statLabel.Position = UDim2.new(0.5, 0, 0, 27) statLabel.Size = UDim2.fromOffset(150, 12) statLabel.BackgroundTransparency = 1 statLabel.Font = Enum.Font.Gotham statLabel.TextSize = 11 statLabel.TextColor3 = COLOR_DIM statLabel.RichText = true statLabel.Text = "" statLabel.Parent = panel gui.Parent = gethui() tween(panel, TW_FADE, { GroupTransparency = 0 }) tween(scale, TW_POP, { Scale = 1 }) -- State local samples = {} -- { {t, pos}, ... } oldest first local lastYaw = nil local groundBaseline = nil local wasAirborne = false local takeoffSpeed = 0 local turnTime, syncTime = 0, 0 local history = {} local shownSpeed = 0 local barState = {} -- bar -> last painted state, so we only tween on change local rayParams = RaycastParams.new() rayParams.FilterType = Enum.RaycastFilterType.Exclude local function fmtSync(v) return v and string.format("%d%%", math.floor(v + 0.5)) or "--" end local function fmtGain(v) return v and string.format("%+.1f", v) or "--" end local function resetMotion() table.clear(samples) lastYaw = nil wasAirborne = false turnTime, syncTime = 0, 0 end local function refreshStatLabel() statLabel.Text = string.format( '%s %s avg %s %s', fmtSync(stats.lastSync), fmtGain(stats.lastGain), fmtSync(stats.avgSync), fmtGain(stats.avgGain) ) end local function resetStats() table.clear(history) stats.jumps = 0 stats.lastSync, stats.lastGain = nil, nil stats.avgSync, stats.avgGain = nil, nil groundBaseline = nil resetMotion() refreshStatLabel() -- Quick squash so the reset reads as deliberate. scale.Scale = 0.9 tween(scale, TW_POP, { Scale = 1 }) stroke.Transparency = 0.4 tween(stroke, TW_SETTLE, { Transparency = 0.88 }) end refreshStatLabel() local function getRoot() local folder = workspace:FindFirstChild("Characters") local char = folder and folder:FindFirstChild(LocalPlayer.Name) or LocalPlayer.Character return char and char:FindFirstChild("HumanoidRootPart"), folder end local function horizontalSpeed(now, pos) local last = samples[#samples] -- A big single-frame jump is a teleport (respawn, reset), not movement. if last and (pos - last[2]).Magnitude > 100 then table.clear(samples) end table.insert(samples, { now, pos }) while #samples > 2 and now - samples[2][1] >= SPEED_WINDOW do table.remove(samples, 1) end local first = samples[1] local span = now - first[1] if span <= 0 then return stats.speed end local d = pos - first[2] return Vector2.new(d.X, d.Z).Magnitude / span end local function isAirborne(root, folder) rayParams.FilterDescendantsInstances = { folder or root.Parent } local hit = workspace:Raycast(root.Position, Vector3.new(0, -RAY_LENGTH, 0), rayParams) if not hit then return true end local dist = root.Position.Y - hit.Position.Y if not groundBaseline or dist < groundBaseline then groundBaseline = dist end return dist > groundBaseline + AIR_MARGIN end local function finishJump(landSpeed) local sync = turnTime > 0 and (syncTime / turnTime * 100) or nil local gain = landSpeed - takeoffSpeed stats.jumps += 1 stats.lastSync = sync stats.lastGain = gain table.insert(history, { sync = sync, gain = gain }) if #history > HISTORY then table.remove(history, 1) end local syncSum, syncN, gainSum = 0, 0, 0 for _, h in history do gainSum += h.gain if h.sync then syncSum += h.sync syncN += 1 end end stats.avgGain = gainSum / #history stats.avgSync = syncN > 0 and syncSum / syncN or nil refreshStatLabel() -- Flash the fresh numbers in the colour of the result, then settle back. statLabel.TextColor3 = gain >= 0 and COLOR_GOOD or COLOR_BAD tween(statLabel, TW_SETTLE, { TextColor3 = COLOR_DIM }) end local function paintBar(bar, isCorrect, isWrong, held) local color, height if isCorrect then color = held and COLOR_GOOD or COLOR_HINT height = held and 26 or 22 elseif isWrong and held then color, height = COLOR_BAD, 22 else color = held and COLOR_HELD or COLOR_IDLE height = held and 20 or 16 end local last = barState[bar] if not last or last.color ~= color or last.height ~= height then barState[bar] = { color = color, height = height } tween(bar, TW_BAR, { BackgroundColor3 = color, Size = UDim2.fromOffset(3, height) }) end end local function step(dt) local root, folder = getRoot() if not root or dt <= 0 then resetMotion() return end local now = os.clock() stats.speed = horizontalSpeed(now, root.Position) local look = workspace.CurrentCamera.CFrame.LookVector local yaw = math.atan2(-look.X, -look.Z) local yawRate = 0 if lastYaw then local d = (yaw - lastYaw + math.pi) % (2 * math.pi) - math.pi yawRate = d / dt end lastYaw = yaw -- Positive yaw rate = turning left. local turning = math.abs(yawRate) > TURN_DEADZONE local turnLeft = turning and yawRate > 0 local turnRight = turning and yawRate < 0 local leftHeld = UserInputService:IsKeyDown(KEY_LEFT) local rightHeld = UserInputService:IsKeyDown(KEY_RIGHT) local airborne = isAirborne(root, folder) stats.airborne = airborne if airborne and not wasAirborne then takeoffSpeed = stats.speed turnTime, syncTime = 0, 0 elseif wasAirborne and not airborne then finishJump(stats.speed) end wasAirborne = airborne if airborne and turning then turnTime += dt local inSync = (turnLeft and leftHeld and not rightHeld) or (turnRight and rightHeld and not leftHeld) if inSync then syncTime += dt end end paintBar(barLeft, turnLeft, turnRight, leftHeld) paintBar(barRight, turnRight, turnLeft, rightHeld) -- Ease the number so it glides instead of flickering digit to digit. shownSpeed += (stats.speed - shownSpeed) * math.min(dt * 14, 1) speedLabel.Text = string.format("%.1f", shownSpeed) end local conn = RunService.RenderStepped:Connect(function(dt) local ok, err = pcall(step, dt) if not ok then stats.lastError = tostring(err) end end) local inputConn = UserInputService.InputBegan:Connect(function(input, processed) if not processed and input.KeyCode == KEY_RESET then resetStats() end end) env.StrafeTrainer = { stats = stats, reset = resetStats, destroy = function() conn:Disconnect() inputConn:Disconnect() gui:Destroy() env.StrafeTrainer = nil end, }