-- Written By Kip Turner, Copyright Roblox 2014 -- Updated by Garnold to utilize the new PathfindingService API, 2017 local UIS = game:GetService("UserInputService") local PathfindingService = game:GetService("PathfindingService") local PlayerService = game:GetService("Players") local RunService = game:GetService("RunService") local DebrisService = game:GetService('Debris') local ReplicatedStorage = game:GetService('ReplicatedStorage') local tweenService = game:GetService("TweenService") local CameraScript = script:FindFirstAncestor("CameraScript") local InvisicamModule = require(CameraScript:WaitForChild("Invisicam")) local OrbitalCamModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('OrbitalCamera')) local Player = PlayerService.LocalPlayer local PlayerScripts = Player.PlayerScripts local ControlScript = PlayerScripts:FindFirstChild("ControlScript") local MasterControl, TouchJump if ControlScript then MasterControl = ControlScript:FindFirstChild("MasterControl") if MasterControl then local TouchJumpModule = MasterControl:FindFirstChild("TouchJump") if TouchJumpModule then TouchJump = require(TouchJumpModule) end end end local SHOW_PATH = true local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList local math_min = math.min local math_max = math.max local math_pi = math.pi local math_atan2 = math.atan2 local Vector3_new = Vector3.new local Vector2_new = Vector2.new local CFrame_new = CFrame.new local CurrentSeatPart = nil local DrivingTo = nil local XZ_VECTOR3 = Vector3_new(1, 0, 1) local ZERO_VECTOR3 = Vector3_new(0, 0, 0) local ZERO_VECTOR2 = Vector2_new(0, 0) local lastFailedPosition = nil local BindableEvent_OnFailStateChanged = nil if UIS.TouchEnabled then BindableEvent_OnFailStateChanged = Instance.new('BindableEvent') BindableEvent_OnFailStateChanged.Name = "OnClickToMoveFailStateChange" BindableEvent_OnFailStateChanged.Parent = PlayerScripts end --------------------------UTIL LIBRARY------------------------------- local Utility = {} do local Signal = {} function Signal.Create() local sig = {} local mSignaler = Instance.new('BindableEvent') local mArgData = nil local mArgDataCount = nil function sig:fire(...) mArgData = {...} mArgDataCount = select('#', ...) mSignaler:Fire() end function sig:connect(f) if not f then error("connect(nil)", 2) end return mSignaler.Event:connect(function() f(unpack(mArgData, 1, mArgDataCount)) end) end function sig:wait() mSignaler.Event:wait() assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.") return unpack(mArgData, 1, mArgDataCount) end return sig end Utility.Signal = Signal function Utility.Create(instanceType) return function(data) local obj = Instance.new(instanceType) for k, v in pairs(data) do if type(k) == 'number' then v.Parent = obj else obj[k] = v end end return obj end end local function ViewSizeX() local camera = workspace.CurrentCamera local x = camera and camera.ViewportSize.X or 0 local y = camera and camera.ViewportSize.Y or 0 if x == 0 then return 1024 else if x > y then return x else return y end end end Utility.ViewSizeX = ViewSizeX local function ViewSizeY() local camera = workspace.CurrentCamera local x = camera and camera.ViewportSize.X or 0 local y = camera and camera.ViewportSize.Y or 0 if y == 0 then return 768 else if x > y then return y else return x end end end Utility.ViewSizeY = ViewSizeY local function FindChacterAncestor(part) if part then local humanoid = part:FindFirstChild("Humanoid") if humanoid then return part, humanoid else return FindChacterAncestor(part.Parent) end end end Utility.FindChacterAncestor = FindChacterAncestor local function Raycast(ray, ignoreNonCollidable, ignoreList) local ignoreList = ignoreList or {} local hitPart, hitPos, hitNorm, hitMat = RayCastIgnoreList(workspace, ray, ignoreList) if hitPart then if ignoreNonCollidable and hitPart.CanCollide == false then table.insert(ignoreList, hitPart) return Raycast(ray, ignoreNonCollidable, ignoreList) end return hitPart, hitPos, hitNorm, hitMat end return nil, nil end Utility.Raycast = Raycast local function AveragePoints(positions) local avgPos = ZERO_VECTOR2 if #positions > 0 then for i = 1, #positions do avgPos = avgPos + positions[i] end avgPos = avgPos / #positions end return avgPos end Utility.AveragePoints = AveragePoints local function FuzzyEquals(numa, numb) return numa + 0.1 > numb and numa - 0.1 < numb end Utility.FuzzyEquals = FuzzyEquals local LastInput = 0 UIS.InputBegan:connect(function(inputObject, wasSunk) if not wasSunk then if inputObject.UserInputType == Enum.UserInputType.Touch or inputObject.UserInputType == Enum.UserInputType.MouseButton1 or inputObject.UserInputType == Enum.UserInputType.MouseButton2 then LastInput = tick() end end end) Utility.GetLastInput = function() return LastInput end end local humanoidCache = {} local function findPlayerHumanoid(player) local character = player and player.Character if character then local resultHumanoid = humanoidCache[player] if resultHumanoid and resultHumanoid.Parent == character then return resultHumanoid else humanoidCache[player] = nil -- Bust Old Cache local humanoid = character:FindFirstChildOfClass("Humanoid") if humanoid then humanoidCache[player] = humanoid end return humanoid end end end --------------------------------------------------------- local Signal = Utility.Signal local Create = Utility.Create --------------------------CHARACTER CONTROL------------------------------- local CurrentIgnoreList local function GetCharacter() return Player and Player.Character end local function GetTorso() local humanoid = findPlayerHumanoid(Player) return humanoid and humanoid.Torso end local function getIgnoreList() if CurrentIgnoreList then return CurrentIgnoreList end CurrentIgnoreList = {} table.insert(CurrentIgnoreList, GetCharacter()) return CurrentIgnoreList end ----------------------------------------------------------------------------- -----------------------------------PATHER-------------------------------------- local function createNewPopup(popupType) local newModel = Instance.new("ImageHandleAdornment") newModel.AlwaysOnTop = false newModel.Image = "rbxasset://textures/Cursors/Gamepad/Pointer@2x.png" newModel.ZIndex = 2 local size = ZERO_VECTOR2 if popupType == "DestinationPopup" then newModel.Color3 = Color3.fromRGB(0, 175, 255) size = Vector2.new(4,4) elseif popupType == "DirectWalkPopup" then newModel.Color3 = Color3.fromRGB(255, 255, 100) size = Vector2.new(4,4) elseif popupType == "FailurePopup" then newModel.Color3 = Color3.fromRGB(255, 100, 100) size = Vector2.new(4,4) elseif popupType == "PatherPopup" then newModel.Color3 = Color3.fromRGB(255, 255, 255) size = Vector2.new(3,3) newModel.ZIndex = 1 end local dataStructure = {} dataStructure.Model = newModel function dataStructure:TweenIn() local tween1 = tweenService:Create(self.Model, TweenInfo.new( 1, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out, 0, false, 0 ),{ Size = size } ) tween1:Play() return tween1 end function dataStructure:TweenOut() local tween1 = tweenService:Create(self.Model, TweenInfo.new( .25, Enum.EasingStyle.Quad, Enum.EasingDirection.In, 0, false, 0 ),{ Size = ZERO_VECTOR2 } ) tween1:Play() return tween1 end function dataStructure:Place(position, dest) -- place the model at position if not self.Model.Parent then self.Model.Parent = workspace.Terrain self.Model.Adornee = workspace.Terrain self.Model.CFrame = CFrame.new(position,dest)*CFrame.Angles(math.pi/2,0,0)-Vector3.new(0,-.2,0) end end return dataStructure end local function createPopupPath(points, numCircles) -- creates a path with the provided points, using the path and number of circles provided local popups = {} local stopTraversing = false local function killPopup(i) -- kill all popups before and at i for iter, v in pairs(popups) do if iter <= i then local tween = v:TweenOut() spawn(function() tween.Completed:wait() v.Model:Destroy() end) popups[iter] = nil end end end local function stopFunction() stopTraversing = true killPopup(#points) end spawn(function() for i = 1, #points do if stopTraversing then break end local includeWaypoint = i % numCircles == 0 and i < #points and (points[#points].Position - points[i].Position).magnitude > 4 if includeWaypoint then local popup = createNewPopup("PatherPopup") popups[i] = popup local nextPopup = points[i+1] popup:Place(points[i].Position, nextPopup and nextPopup.Position or points[#points].Position) local tween = popup:TweenIn() wait(0.2) end end end) return stopFunction, killPopup end local function Pather(character, endPoint, surfaceNormal) local this = {} this.Cancelled = false this.Started = false this.Finished = Signal.Create() this.PathFailed = Signal.Create() this.PathStarted = Signal.Create() this.PathComputing = false this.PathComputed = false this.TargetPoint = endPoint this.TargetSurfaceNormal = surfaceNormal this.MoveToConn = nil this.CurrentPoint = 0 function this:Cleanup() if this.stopTraverseFunc then this.stopTraverseFunc() end if this.MoveToConn then this.MoveToConn:disconnect() this.MoveToConn = nil this.humanoid = nil end this.humanoid = nil end function this:Cancel() this.Cancelled = true this:Cleanup() end function this:ComputePath() local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso local success = false if torso then if this.PathComputed or this.PathComputing then return end this.PathComputing = true success = pcall(function() this.pathResult = PathfindingService:FindPathAsync(torso.CFrame.p, this.TargetPoint) end) this.pointList = this.pathResult and this.pathResult:GetWaypoints() this.PathComputing = false this.PathComputed = this.pathResult and this.pathResult.Status == Enum.PathStatus.Success or false end return true end function this:IsValidPath() if not this.pathResult then this:ComputePath() end return this.pathResult.Status == Enum.PathStatus.Success end function this:OnPointReached(reached) if reached and not this.Cancelled then this.CurrentPoint = this.CurrentPoint + 1 if this.CurrentPoint > #this.pointList then -- End of path reached if this.stopTraverseFunc then this.stopTraverseFunc() end this.Finished:fire() this:Cleanup() else -- If next action == Jump, but the humanoid -- is still jumping from a previous action -- wait until it gets to the ground if this.CurrentPoint + 1 <= #this.pointList then local nextAction = this.pointList[this.CurrentPoint + 1].Action if nextAction == Enum.PathWaypointAction.Jump then local currentState = this.humanoid:GetState() if currentState == Enum.HumanoidStateType.FallingDown or currentState == Enum.HumanoidStateType.Freefall or currentState == Enum.HumanoidStateType.Jumping then this.humanoid.FreeFalling:wait() -- Give time to the humanoid's state to change -- Otherwise, the jump flag in Humanoid -- will be reset by the state change wait(0.1) end end end -- Move to the next point if this.setPointFunc then this.setPointFunc(this.CurrentPoint) end local nextWaypoint = this.pointList[this.CurrentPoint] if nextWaypoint.Action == Enum.PathWaypointAction.Jump then this.humanoid.Jump = true end this.humanoid:MoveTo(nextWaypoint.Position) end else this.PathFailed:fire() this:Cleanup() end end function this:Start() if CurrentSeatPart then return end this.humanoid = findPlayerHumanoid(Player) if this.Started then return end this.Started = true if SHOW_PATH then -- choose whichever one Mike likes best this.stopTraverseFunc, this.setPointFunc = createPopupPath(this.pointList, 4) end if #this.pointList > 0 then this.MoveToConn = this.humanoid.MoveToFinished:connect(function(reached) this:OnPointReached(reached) end) this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it. this:OnPointReached(true) -- Move to first point else this.PathFailed:fire() if this.stopTraverseFunc then this.stopTraverseFunc() end end end this:ComputePath() if not this.PathComputed then -- set the end point towards the camera and raycasted towards the ground in case we hit a wall local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5 local ray = Ray.new(offsetPoint, Vector3_new(0,-1,0)*50) local newHitPart, newHitPos = RayCastIgnoreList(workspace, ray, getIgnoreList()) if newHitPart then this.TargetPoint = newHitPos end -- try again this:ComputePath() end return this end ------------------------------------------------------------------------- local function IsInBottomLeft(pt) local joystickHeight = math_min(Utility.ViewSizeY() * 0.33, 250) local joystickWidth = joystickHeight return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight end local function IsInBottomRight(pt) local joystickHeight = math_min(Utility.ViewSizeY() * 0.33, 250) local joystickWidth = joystickHeight return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight end local function CheckAlive(character) local humanoid = findPlayerHumanoid(Player) return humanoid ~= nil and humanoid.Health > 0 end local function GetEquippedTool(character) if character ~= nil then for _, child in pairs(character:GetChildren()) do if child:IsA('Tool') then return child end end end end local ExistingPather = nil local ExistingIndicator = nil local PathCompleteListener = nil local PathFailedListener = nil local function CleanupPath() DrivingTo = nil if ExistingPather then ExistingPather:Cancel() end if PathCompleteListener then PathCompleteListener:disconnect() PathCompleteListener = nil end if PathFailedListener then PathFailedListener:disconnect() PathFailedListener = nil end if ExistingIndicator then local obj = ExistingIndicator local tween = obj:TweenOut() local tweenCompleteEvent = nil tweenCompleteEvent = tween.Completed:connect(function() tweenCompleteEvent:disconnect() obj.Model:Destroy() end) ExistingIndicator = nil end end local function getExtentsSize(Parts) local maxX,maxY,maxZ = -math.huge,-math.huge,-math.huge local minX,minY,minZ = math.huge,math.huge,math.huge for i = 1, #Parts do maxX,maxY,maxZ = math_max(maxX, Parts[i].Position.X), math_max(maxY, Parts[i].Position.Y), math_max(maxZ, Parts[i].Position.Z) minX,minY,minZ = math_min(minX, Parts[i].Position.X), math_min(minY, Parts[i].Position.Y), math_min(minZ, Parts[i].Position.Z) end return Region3.new(Vector3_new(minX, minY, minZ), Vector3_new(maxX, maxY, maxZ)) end local function inExtents(Extents, Position) if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then return false end if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then return false end --ignoring Y for now return true end local FailCount = 0 local function OnTap(tapPositions, goToPoint) -- Good to remember if this is the latest tap event local camera = workspace.CurrentCamera local character = Player.Character if not CheckAlive(character) then return end -- This is a path tap position if #tapPositions == 1 or goToPoint then if camera then local unitRay = camera:ScreenPointToRay(tapPositions[1].x, tapPositions[1].y) local ray = Ray.new(unitRay.Origin, unitRay.Direction*400) -- inivisicam stuff local initIgnore = getIgnoreList() local invisicamParts = InvisicamModule:GetObscuredParts() local ignoreTab = {} -- add to the ignore list for i, v in pairs(invisicamParts) do ignoreTab[#ignoreTab+1] = i end for i = 1, #initIgnore do ignoreTab[#ignoreTab+1] = initIgnore[i] end -- local myHumanoid = findPlayerHumanoid(Player) local hitPart, hitPt, hitNormal, hitMat = Utility.Raycast(ray, true, ignoreTab) local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart) local torso = GetTorso() local startPos = torso.CFrame.p if goToPoint then hitPt = goToPoint hitChar = nil end if hitChar and hitHumanoid and hitHumanoid.Torso and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then CleanupPath() if myHumanoid then myHumanoid:MoveTo(hitPt) end -- Do shoot local currentWeapon = GetEquippedTool(character) if currentWeapon then currentWeapon:Activate() LastFired = tick() end elseif hitPt and character and not CurrentSeatPart then local thisPather = Pather(character, hitPt, hitNormal) if thisPather:IsValidPath() then FailCount = 0 thisPather:Start() if BindableEvent_OnFailStateChanged then BindableEvent_OnFailStateChanged:Fire(false) end CleanupPath() local destinationPopup = createNewPopup("DestinationPopup") destinationPopup:Place(hitPt, Vector3_new(0,hitPt.y,0)) local failurePopup = createNewPopup("FailurePopup") local currentTween = destinationPopup:TweenIn() ExistingPather = thisPather ExistingIndicator = destinationPopup PathCompleteListener = thisPather.Finished:connect(function() if destinationPopup then if ExistingIndicator == destinationPopup then ExistingIndicator = nil end local tween = destinationPopup:TweenOut() local tweenCompleteEvent = nil tweenCompleteEvent = tween.Completed:connect(function() tweenCompleteEvent:disconnect() destinationPopup.Model:Destroy() destinationPopup = nil end) end if hitChar then local humanoid = findPlayerHumanoid(Player) local currentWeapon = GetEquippedTool(character) if currentWeapon then currentWeapon:Activate() LastFired = tick() end if humanoid then humanoid:MoveTo(hitPt) end end end) PathFailedListener = thisPather.PathFailed:connect(function() if failurePopup then failurePopup:Place(hitPt, Vector3_new(0,hitPt.y,0)) local failTweenIn = failurePopup:TweenIn() failTweenIn.Completed:wait() local failTweenOut = failurePopup:TweenOut() failTweenOut.Completed:wait() failurePopup.Model:Destroy() failurePopup = nil end end) else if hitPt then -- Feedback here for when we don't have a good path local foundDirectPath = false if (hitPt-startPos).Magnitude < 25 and (startPos.y-hitPt.y > -3) then -- move directly here if myHumanoid then if myHumanoid.Sit then myHumanoid.Jump = true end myHumanoid:MoveTo(hitPt) foundDirectPath = true end end spawn(function() local directPopup = createNewPopup(foundDirectPath and "DirectWalkPopup" or "FailurePopup") directPopup:Place(hitPt, Vector3_new(0,hitPt.y,0)) local directTweenIn = directPopup:TweenIn() directTweenIn.Completed:wait() local directTweenOut = directPopup:TweenOut() directTweenOut.Completed:wait() directPopup.Model:Destroy() directPopup = nil end) end end elseif hitPt and character and CurrentSeatPart then local destinationPopup = createNewPopup("DestinationPopup") ExistingIndicator = destinationPopup destinationPopup:Place(hitPt, Vector3_new(0,hitPt.y,0)) destinationPopup:TweenIn() DrivingTo = hitPt local ConnectedParts = CurrentSeatPart:GetConnectedParts(true) while wait() do if CurrentSeatPart and ExistingIndicator == destinationPopup then local ExtentsSize = getExtentsSize(ConnectedParts) if inExtents(ExtentsSize, hitPt) then local popup = destinationPopup spawn(function() local tweenOut = popup:TweenOut() tweenOut.Completed:wait() popup.Model:Destroy() end) destinationPopup = nil DrivingTo = nil break end else if CurrentSeatPart == nil and destinationPopup == ExistingIndicator then DrivingTo = nil OnTap(tapPositions, hitPt) end local popup = destinationPopup spawn(function() local tweenOut = popup:TweenOut() tweenOut.Completed:wait() popup.Model:Destroy() end) destinationPopup = nil break end end end end elseif #tapPositions >= 2 then if camera then -- Do shoot local avgPoint = Utility.AveragePoints(tapPositions) local unitRay = camera:ScreenPointToRay(avgPoint.x, avgPoint.y) local currentWeapon = GetEquippedTool(character) if currentWeapon then currentWeapon:Activate() LastFired = tick() end end end end local function CreateClickToMoveModule() local this = {} local LastStateChange = 0 local LastState = Enum.HumanoidStateType.Running local FingerTouches = {} local NumUnsunkTouches = 0 -- PC simulation local mouse1Down = tick() local mouse1DownPos = Vector2_new() local mouse2Down = tick() local mouse2DownPos = Vector2_new() local mouse2Up = tick() local movementKeys = { [Enum.KeyCode.W] = true; [Enum.KeyCode.A] = true; [Enum.KeyCode.S] = true; [Enum.KeyCode.D] = true; [Enum.KeyCode.Up] = true; [Enum.KeyCode.Down] = true; } local TapConn = nil local InputBeganConn = nil local InputChangedConn = nil local InputEndedConn = nil local HumanoidDiedConn = nil local CharacterChildAddedConn = nil local OnCharacterAddedConn = nil local CharacterChildRemovedConn = nil local RenderSteppedConn = nil local HumanoidSeatedConn = nil local function disconnectEvent(event) if event then event:disconnect() end end local function DisconnectEvents() disconnectEvent(TapConn) disconnectEvent(InputBeganConn) disconnectEvent(InputChangedConn) disconnectEvent(InputEndedConn) disconnectEvent(HumanoidDiedConn) disconnectEvent(CharacterChildAddedConn) disconnectEvent(OnCharacterAddedConn) disconnectEvent(RenderSteppedConn) disconnectEvent(CharacterChildRemovedConn) pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end) disconnectEvent(HumanoidSeatedConn) end local function IsFinite(num) return num == num and num ~= 1/0 and num ~= -1/0 end local function findAngleBetweenXZVectors(vec2, vec1) return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z) end -- Setup the camera CameraModule = OrbitalCamModule() local function OnTouchBegan(input, processed) if FingerTouches[input] == nil and not processed then NumUnsunkTouches = NumUnsunkTouches + 1 end FingerTouches[input] = processed end local function OnTouchChanged(input, processed) if FingerTouches[input] == nil then FingerTouches[input] = processed if not processed then NumUnsunkTouches = NumUnsunkTouches + 1 end end end local function OnTouchEnded(input, processed) if FingerTouches[input] ~= nil and FingerTouches[input] == false then NumUnsunkTouches = NumUnsunkTouches - 1 end FingerTouches[input] = nil end local function OnCharacterAdded(character) DisconnectEvents() InputBeganConn = UIS.InputBegan:connect(function(input, processed) if input.UserInputType == Enum.UserInputType.Touch then OnTouchBegan(input, processed) -- Give back controls when they tap both sticks local wasInBottomLeft = IsInBottomLeft(input.Position) local wasInBottomRight = IsInBottomRight(input.Position) if wasInBottomRight or wasInBottomLeft then for otherInput, _ in pairs(FingerTouches) do if otherInput ~= input then local otherInputInLeft = IsInBottomLeft(otherInput.Position) local otherInputInRight = IsInBottomRight(otherInput.Position) if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then if BindableEvent_OnFailStateChanged then BindableEvent_OnFailStateChanged:Fire(true) end return end end end end end -- Cancel path when you use the keyboard controls. if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then CleanupPath() end if input.UserInputType == Enum.UserInputType.MouseButton1 then mouse1Down = tick() mouse1DownPos = input.Position end if input.UserInputType == Enum.UserInputType.MouseButton2 then mouse2Down = tick() mouse2DownPos = input.Position end end) InputChangedConn = UIS.InputChanged:connect(function(input, processed) if input.UserInputType == Enum.UserInputType.Touch then OnTouchChanged(input, processed) end end) InputEndedConn = UIS.InputEnded:connect(function(input, processed) if input.UserInputType == Enum.UserInputType.Touch then OnTouchEnded(input, processed) end if input.UserInputType == Enum.UserInputType.MouseButton2 then mouse2Up = tick() local currPos = input.Position if mouse2Up - mouse2Down < 0.25 and (currPos - mouse2DownPos).magnitude < 5 then local positions = {currPos} OnTap(positions) end end end) TapConn = UIS.TouchTap:connect(function(touchPositions, processed) if not processed then OnTap(touchPositions) end end) local function computeThrottle(dist) if dist > .2 then return 0.5+(dist^2)/2 else return 0 end end local function getThrottleAndSteer(object, point) local throttle, steer = 0, 0 local oCF = object.CFrame local relativePosition = oCF:pointToObjectSpace(point) local relativeZDirection = -relativePosition.z local relativeDistance = relativePosition.magnitude -- throttle quadratically increases from 0-1 as distance from the selected point goes from 0-50, after 50, throttle is 1. -- this allows shorter distance travel to have more fine-tuned control. throttle = computeThrottle(math_min(1,relativeDistance/50))*math.sign(relativeZDirection) local steerAngle = -math_atan2(-relativePosition.x, -relativePosition.z) steer = steerAngle/(math_pi/4) return throttle, steer end local function Update() if CameraModule then CameraModule.UserPanningTheCamera = true if CameraModule.UserPanningTheCamera then CameraModule.UpdateTweenFunction = nil else if CameraModule.UpdateTweenFunction then local done = CameraModule.UpdateTweenFunction() if done then CameraModule.UpdateTweenFunction = nil end end end CameraModule:Update() end if CurrentSeatPart then if DrivingTo then local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo) CurrentSeatPart.ThrottleFloat = throttle CurrentSeatPart.SteerFloat = steer else CurrentSeatPart.ThrottleFloat = 0 CurrentSeatPart.SteerFloat = 0 end end end RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update) local function onSeated(child, active, currentSeatPart) if active then if TouchJump and UIS.TouchEnabled then TouchJump:Enable() end if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then CurrentSeatPart = currentSeatPart end else CurrentSeatPart = nil if TouchJump and UIS.TouchEnabled then TouchJump:Disable() end end end local function OnCharacterChildAdded(child) if UIS.TouchEnabled then if child:IsA('Tool') then child.ManualActivationOnly = true end end if child:IsA('Humanoid') then disconnectEvent(HumanoidDiedConn) HumanoidDiedConn = child.Died:connect(function() if ExistingIndicator then DebrisService:AddItem(ExistingIndicator.Model, 1) end end) HumanoidSeatedConn = child.Seated:connect(function(active, seat) onSeated(child, active, seat) end) if child.SeatPart then onSeated(child, true, child.SeatPart) end end end CharacterChildAddedConn = character.ChildAdded:connect(function(child) OnCharacterChildAdded(child) end) CharacterChildRemovedConn = character.ChildRemoved:connect(function(child) if UIS.TouchEnabled then if child:IsA('Tool') then child.ManualActivationOnly = false end end end) for _, child in pairs(character:GetChildren()) do OnCharacterChildAdded(child) end end local Running = false function this:Stop() if Running then DisconnectEvents() CleanupPath() if CameraModule then CameraModule.UpdateTweenFunction = nil CameraModule:SetEnabled(false) end -- Restore tool activation on shutdown if UIS.TouchEnabled then local character = Player.Character if character then for _, child in pairs(character:GetChildren()) do if child:IsA('Tool') then child.ManualActivationOnly = false end end end end DrivingTo = nil Running = false end end function this:Start() if not Running then if Player.Character then -- retro-listen OnCharacterAdded(Player.Character) end OnCharacterAddedConn = Player.CharacterAdded:connect(OnCharacterAdded) if CameraModule then CameraModule:SetEnabled(true) end Running = true end end return this end return CreateClickToMoveModule