-- ===== SETTINGS ===== local TOOL_NAME = "MoveTo" -- name of the tool that appears in the backpack local MODE = "Path" -- "Path" = PathfindingService (goes around walls), "Straight" = walks in a straight line local STAND_UP_IF_SEATED = true -- true = stand up automatically if seated before moving, false = do nothing while seated local MAX_DISTANCE = 300 -- max studs from the character you can click to move to local MAX_SLOPE_DEGREES = 40 -- surfaces steeper than this are rejected as targets local AGENT_RADIUS = 2 -- character width used by pathfinding (studs) local AGENT_HEIGHT = 5 -- character height used by pathfinding (studs) local AGENT_CAN_JUMP = true -- allow paths that include jumps local WAYPOINT_TIMEOUT = 4 -- seconds allowed to reach one waypoint before the path is recalculated local MAX_REPATHS = 3 -- how many times the path can be recalculated when blocked local CLICK_COOLDOWN = 0.2 -- minimum seconds between two clicks local CANCEL_ON_MANUAL_MOVE = true -- stop moving when the player presses a movement key or moves the thumbstick local MANUAL_INPUT_DEADZONE = 0.2 -- thumbstick deadzone for manual move cancel (0 to 1) local DISABLE_DEFAULT_CLICK_TO_MOVE = true -- turn off Roblox built-in click-to-move while the tool is equipped local SHOW_MARKER = true -- show a marker on the destination local MARKER_SIZE = 1.2 -- marker diameter (studs) local MARKER_COLOR_VALID = Color3.fromRGB(80, 200, 255) -- marker color for a valid destination local MARKER_COLOR_INVALID = Color3.fromRGB(255, 70, 70) -- marker color for an invalid destination -- ===== END SETTINGS ===== local Players = game:GetService("Players") local PathfindingService = game:GetService("PathfindingService") local UserInputService = game:GetService("UserInputService") local GuiService = game:GetService("GuiService") local env = (getgenv and getgenv()) or _G local MARK = "MoveToInjected" local player = Players.LocalPlayer local MOVE_KEYS = { [Enum.KeyCode.W] = true, [Enum.KeyCode.A] = true, [Enum.KeyCode.S] = true, [Enum.KeyCode.D] = true, [Enum.KeyCode.Space] = true, [Enum.KeyCode.Up] = true, [Enum.KeyCode.Down] = true, [Enum.KeyCode.Left] = true, [Enum.KeyCode.Right] = true, } local tool local character local humanoid local rootPart local diedConnection local marker local activeToken = 0 local activeConnections = {} local toolConnections = {} local lastClickTime = 0 local repathRequested = false local function ensureMarker() if marker or not SHOW_MARKER then return end marker = Instance.new("Part") marker.Name = "MoveToMarker" marker.Shape = Enum.PartType.Ball marker.Size = Vector3.new(MARKER_SIZE, MARKER_SIZE, MARKER_SIZE) marker.Anchored = true marker.CanCollide = false marker.CanQuery = false marker.CanTouch = false marker.CastShadow = false marker.Material = Enum.Material.Neon marker.Transparency = 0.3 end local function showMarker(position, valid) if not SHOW_MARKER then return end ensureMarker() marker.Color = valid and MARKER_COLOR_VALID or MARKER_COLOR_INVALID marker.Position = position + Vector3.new(0, MARKER_SIZE / 2, 0) marker.Parent = workspace end local function hideMarker() if marker then marker.Parent = nil end end local function destroyMarker() if marker then marker:Destroy() marker = nil end end local function stopMovement() activeToken += 1 for _, connection in activeConnections do connection:Disconnect() end table.clear(activeConnections) if humanoid and rootPart and humanoid.Health > 0 and not humanoid.Sit then humanoid:MoveTo(rootPart.Position) end hideMarker() end local function bindCharacter() character = player.Character if not character then return false end humanoid = character:FindFirstChildOfClass("Humanoid") rootPart = character:FindFirstChild("HumanoidRootPart") if not humanoid or not rootPart then return false end if diedConnection then diedConnection:Disconnect() end diedConnection = humanoid.Died:Connect(function() stopMovement() end) return true end local function isPointerOverGui() local playerGui = player:FindFirstChildOfClass("PlayerGui") if not playerGui then return false end local mouse = UserInputService:GetMouseLocation() local inset = GuiService:GetGuiInset() for _, guiObject in playerGui:GetGuiObjectsAtPosition(mouse.X, mouse.Y - inset.Y) do if guiObject:IsA("GuiButton") or guiObject:IsA("TextBox") or guiObject.Active then return true end end return false end local function getMouseHit() local mouse = UserInputService:GetMouseLocation() local ray = workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y) local params = RaycastParams.new() params.FilterType = Enum.RaycastFilterType.Exclude params.FilterDescendantsInstances = { character, tool } return workspace:Raycast(ray.Origin, ray.Direction * 1000, params) end local function isValidTarget(hit) if not hit then return false end local minUpDot = math.cos(math.rad(MAX_SLOPE_DEGREES)) if hit.Normal.Y < minUpDot then return false end return (hit.Position - rootPart.Position).Magnitude <= MAX_DISTANCE end local function waitForMove(timeout, token) local finished = false local reached = false local connection = humanoid.MoveToFinished:Connect(function(success) finished = true reached = success end) local startTime = os.clock() while not finished and token == activeToken and not repathRequested do if os.clock() - startTime > timeout then break end task.wait() end connection:Disconnect() return finished and reached end local function followWaypoints(waypoints, token) for index, waypoint in waypoints do if index == 1 then continue end if token ~= activeToken then return "cancelled" end if waypoint.Action == Enum.PathWaypointAction.Jump then humanoid.Jump = true end humanoid:MoveTo(waypoint.Position) if not waitForMove(WAYPOINT_TIMEOUT, token) then if token ~= activeToken then return "cancelled" end return "repath" end end return "done" end local function runMove(target, token) if MODE == "Straight" then humanoid:MoveTo(target) local speed = math.max(humanoid.WalkSpeed, 1) local timeout = (target - rootPart.Position).Magnitude / speed * 2 + 2 waitForMove(timeout, token) return end for _ = 1, MAX_REPATHS do if token ~= activeToken then return end repathRequested = false local path = PathfindingService:CreatePath({ AgentRadius = AGENT_RADIUS, AgentHeight = AGENT_HEIGHT, AgentCanJump = AGENT_CAN_JUMP, }) local ok = pcall(function() path:ComputeAsync(rootPart.Position, target) end) if not ok or path.Status ~= Enum.PathStatus.Success then return end local blockedConnection = path.Blocked:Connect(function() repathRequested = true end) table.insert(activeConnections, blockedConnection) local result = followWaypoints(path:GetWaypoints(), token) blockedConnection:Disconnect() if result ~= "repath" then return end end end local function bindCancelInputs() table.insert(activeConnections, UserInputService.InputBegan:Connect(function(input, gameProcessed) if gameProcessed or not CANCEL_ON_MANUAL_MOVE then return end if MOVE_KEYS[input.KeyCode] then stopMovement() end end)) table.insert(activeConnections, UserInputService.InputChanged:Connect(function(input) if not CANCEL_ON_MANUAL_MOVE then return end if input.KeyCode == Enum.KeyCode.Thumbstick1 and input.Position.Magnitude > MANUAL_INPUT_DEADZONE then stopMovement() end end)) end local function setDefaultClickToMove(enabled) if not DISABLE_DEFAULT_CLICK_TO_MOVE then return end pcall(function() local playerScripts = player:FindFirstChild("PlayerScripts") local moduleScript = playerScripts and playerScripts:FindFirstChild("PlayerModule") if not moduleScript then return end local controlModule = require(moduleScript) local clickToMove = controlModule:GetClickToMoveController() if enabled then clickToMove:Enable() else clickToMove:Disable() end end) end local function clearTaggedTools(container) if not container then return end for _, item in container:GetChildren() do if item:IsA("Tool") and item:GetAttribute(MARK) then item:Destroy() end end end local function removeTaggedTools() clearTaggedTools(player:FindFirstChildOfClass("Backpack")) clearTaggedTools(player.Character) end local function onActivated() local now = os.clock() if now - lastClickTime < CLICK_COOLDOWN then return end lastClickTime = now if player.Character ~= character or not humanoid then if not bindCharacter() then return end end if humanoid.Health <= 0 or isPointerOverGui() then return end local hit = getMouseHit() stopMovement() local valid = isValidTarget(hit) if hit then showMarker(hit.Position, valid) end if not valid then return end local target = hit.Position local token = activeToken bindCancelInputs() task.spawn(function() if humanoid.Sit then if not STAND_UP_IF_SEATED then stopMovement() return end humanoid.Sit = false humanoid.Jump = true task.wait(0.2) if token ~= activeToken then return end end runMove(target, token) if token == activeToken then stopMovement() end end) end local function fullCleanup() for _, connection in toolConnections do connection:Disconnect() end table.clear(toolConnections) stopMovement() if diedConnection then diedConnection:Disconnect() diedConnection = nil end setDefaultClickToMove(true) destroyMarker() if tool then tool:Destroy() tool = nil end removeTaggedTools() end if env.MoveToCleanup then pcall(env.MoveToCleanup) env.MoveToCleanup = nil end removeTaggedTools() tool = Instance.new("Tool") tool.Name = TOOL_NAME tool.RequiresHandle = true tool.CanBeDropped = false tool:SetAttribute(MARK, true) local handle = Instance.new("Part") handle.Name = "Handle" handle.Size = Vector3.new(0.5, 0.5, 0.5) handle.Transparency = 1 handle.CanCollide = false handle.Massless = true handle.Parent = tool table.insert(toolConnections, tool.Equipped:Connect(function() bindCharacter() setDefaultClickToMove(false) end)) table.insert(toolConnections, tool.Unequipped:Connect(function() stopMovement() setDefaultClickToMove(true) end)) table.insert(toolConnections, tool.Activated:Connect(onActivated)) env.MoveToCleanup = fullCleanup local backpack = player:WaitForChild("Backpack") tool.Parent = backpack