-- Written By Kip Turner, Copyright Roblox 2014 local newClickToMove = script:FindFirstChild("NewClickToMove") if newClickToMove then local newClickToMoveFlagSuccess, newClickToMoveFlagEnabled = pcall(function() return UserSettings():IsUserFeatureEnabled("UserUseNewClickToMove") end) local useNewClickToMove = newClickToMoveFlagSuccess and newClickToMoveFlagEnabled if useNewClickToMove then return require(newClickToMove) end end 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 CameraScript = script.Parent local ClassicCameraModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('ClassicCamera')) local Player = PlayerService.localPlayer local MyMouse = Player:GetMouse() local DirectPathEnabled = false local SHOW_PATH = false local Y_VECTOR3 = Vector3.new(0, 1, 0) local XZ_VECTOR3 = Vector3.new(1, 0, 1) local ZERO_VECTOR3 = Vector3.new(0, 0, 0) local ZERO_VECTOR2 = Vector2.new(0, 0) local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList local GetPartsTouchingExtents = workspace.FindPartsInRegion3 local math_min = math.min local math_max = math.max local math_pi = math.pi local math_floor = math.floor local math_abs = math.abs local math_deg = math.deg local math_acos = math.acos local math_sin = math.sin 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 -- Bindable for when we want touch emergency controls -- TODO: Click to move should probably have it's own gui touch controls -- to manage this. local BindableEvent_OnFailStateChanged = nil local BindableEvent_EnableTouchJump = nil if UIS.TouchEnabled then BindableEvent_OnFailStateChanged = Instance.new('BindableEvent') BindableEvent_OnFailStateChanged.Name = "OnClickToMoveFailStateChange" BindableEvent_EnableTouchJump = Instance.new('BindableEvent') BindableEvent_EnableTouchJump.Name = "EnableTouchJump" local CameraScript = script.Parent local PlayerScripts = CameraScript.Parent BindableEvent_OnFailStateChanged.Parent = PlayerScripts BindableEvent_EnableTouchJump.Parent = PlayerScripts end local function clamp(low, high, num) return (num > high and high or num < low and low or num) 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 AspectRatio() return ViewSizeX() / ViewSizeY() end Utility.AspectRatio = AspectRatio 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 GetUnitRay(x, y, viewWidth, viewHeight, camera) return camera:ScreenPointToRay(x, y) end Utility.GetUnitRay = GetUnitRay local function Raycast(ray, ignoreNonCollidable, ignoreList) local ignoreList = ignoreList or {} local hitPart, hitPos = 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 end return nil, nil end Utility.Raycast = Raycast Utility.Round = function(num, roundToNearest) roundToNearest = roundToNearest or 1 return math_floor((num + roundToNearest/2) / roundToNearest) * roundToNearest end 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 GetThetaBetweenCFrames; do local components = CFrame.new().components local inverse = CFrame.new().inverse local acos = math.acos GetThetaBetweenCFrames = function(c0, c1) -- (CFrame from, CFrame to) -> (float theta) local _, _, _, xx, yx, zx, xy, yy, zy, xz, yz, zz = components(inverse(c0)*c1) local cosTheta = (xx + yy + zz - 1)/2 if cosTheta >= 0.999 then -- Same rotation return 0 elseif cosTheta <= -0.999 then -- Oposite rotations return math_pi else return acos(cosTheta) end end end --------------------------------------------------------- local Signal = Utility.Signal local Create = Utility.Create --------------------------CHARACTER CONTROL------------------------------- local function CreateController() local this = {} this.TorsoLookPoint = nil function this:SetTorsoLookPoint(point) local humanoid = findPlayerHumanoid(Player) if humanoid then humanoid.AutoRotate = false end this.TorsoLookPoint = point self:UpdateTorso() delay(2, function() -- this isnt technically correct for detecting if this is the last issue to the setTorso function if this.TorsoLookPoint == point then this.TorsoLookPoint = nil if humanoid then humanoid.AutoRotate = true end end end) end function this:UpdateTorso(point) if this.TorsoLookPoint then point = this.TorsoLookPoint else return end local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso if torso then local lookVec = (point - torso.CFrame.p).unit local squashedLookVec = Vector3_new(lookVec.X, 0, lookVec.Z).unit torso.CFrame = CFrame.new(torso.CFrame.p, torso.CFrame.p + squashedLookVec) end end return this end local CharacterControl = CreateController() ----------------------------------------------------------------------- --------------------------PC AUTO JUMPER------------------------------- local function GetCharacter() return Player and Player.Character end local function GetTorso() local humanoid = findPlayerHumanoid(Player) return humanoid and humanoid.Torso end local function IsPartAHumanoid(part) return part and part.Parent and (part.Parent:FindFirstChild('Humanoid') ~= nil) end local function doAutoJump() local character = GetCharacter() if (character == nil) then return; end local humanoid = findPlayerHumanoid(Player) if (humanoid == nil) then return; end local rayLength = 1.5; -- This is how high a ROBLOXian jumps from the mid point of his torso local jumpHeight = 7.0; local torso = GetTorso() if (torso == nil) then return; end local torsoCFrame = torso.CFrame; local torsoLookVector = torsoCFrame.lookVector; local torsoPos = torsoCFrame.p; local torsoRay = Ray.new(torsoPos + Vector3_new(0, -torso.Size.Y/2, 0), torsoLookVector * rayLength); local jumpRay = Ray.new(torsoPos + Vector3_new(0, jumpHeight - torso.Size.Y, 0), torsoLookVector * rayLength); local hitPart, _ = RayCastIgnoreList(workspace, torsoRay, {character}, false) local jumpHitPart, _ = RayCastIgnoreList(workspace, jumpRay, {character}, false) if (hitPart and jumpHitPart == nil and hitPart.CanCollide == true) then -- NOTE: this follow line is not in the C++ impl, but an improvement in Click to Move if not IsPartAHumanoid(hitPart) then humanoid.Jump = true; end end end local NO_JUMP_STATES = { [Enum.HumanoidStateType.FallingDown] = false; [Enum.HumanoidStateType.Flying] = false; [Enum.HumanoidStateType.Freefall] = false; [Enum.HumanoidStateType.GettingUp] = false; [Enum.HumanoidStateType.Ragdoll] = false; [Enum.HumanoidStateType.Running] = false; [Enum.HumanoidStateType.Seated] = false; [Enum.HumanoidStateType.Swimming] = false; -- Special case to detect if we are on a ladder [Enum.HumanoidStateType.Climbing] = false; } local function enableAutoJump() local humanoid = findPlayerHumanoid(Player) local currentState = humanoid and humanoid:GetState() if currentState then return NO_JUMP_STATES[currentState] == nil end return false end local function getAutoJump() return true end local function vec3IsZero(vec3) return vec3.magnitude < 0.05 end -- NOTE: This function is radically different from the engine's implementation local walkVelocityVector = Vector3_new(1,1,1) local function calcDesiredWalkVelocity() -- TEMP return walkVelocityVector end local function preStepSimulatorSide(dt) if getAutoJump() and enableAutoJump() then local desiredWalkVelocity = calcDesiredWalkVelocity(); if (not vec3IsZero(desiredWalkVelocity)) then doAutoJump(); end end end local function AutoJumper() local this = {} local running = false local runRoutine = nil function this:Run() running = true local thisRoutine = nil thisRoutine = coroutine.create(function() while running and thisRoutine == runRoutine do this:Step() wait() end end) runRoutine = thisRoutine coroutine.resume(thisRoutine) end function this:Stop() running = false end function this:Step() preStepSimulatorSide() end return this end ----------------------------------------------------------------------------- -----------------------------------PATHER-------------------------------------- local function CreateDestinationIndicator(pos) local destinationGlobe = Create'Part' { Name = 'PathGlobe'; TopSurface = 'Smooth'; BottomSurface = 'Smooth'; Shape = 'Ball'; CanCollide = false; Size = Vector3_new(2,2,2); BrickColor = BrickColor.new('Institutional white'); Transparency = 0; Anchored = true; CFrame = CFrame.new(pos); } return destinationGlobe end local function Pather(character, point) local this = {} this.Cancelled = false this.Started = false this.Finished = Signal.Create() this.PathFailed = Signal.Create() this.PathStarted = Signal.Create() this.PathComputed = false function this:YieldUntilPointReached(character, point, timeout) timeout = timeout or 10000000 local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso local start = tick() local lastMoveTo = start while torso and tick() - start < timeout and this.Cancelled == false do local diffVector = (point - torso.CFrame.p) local xzMagnitude = (diffVector * XZ_VECTOR3).magnitude if xzMagnitude < 6 then -- Jump if the path is telling is to go upwards if diffVector.Y >= 2.2 then humanoid.Jump = true end end -- The hard-coded number 2 here is from the engine's MoveTo implementation if xzMagnitude < 2 then return true end -- Keep on issuing the move command because it will automatically quit every so often. if tick() - lastMoveTo > 1.5 then humanoid:MoveTo(point) lastMoveTo = tick() end CharacterControl:UpdateTorso(point) wait() end return false end function this:Cancel() this.Cancelled = true local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso if humanoid and torso then humanoid:MoveTo(torso.CFrame.p) end end function this:CheckOcclusion(point1, point2, character, torsoRadius) local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso if torsoRadius == nil then torsoRadius = torso and Vector3_new(torso.Size.X/2,0,torso.Size.Z/2) or XZ_VECTOR3 end local diffVector = point2 - point1 local directionVector = diffVector.unit local rightVector = Y_VECTOR3:Cross(directionVector) * torsoRadius local rightPart, _ = Utility.Raycast(Ray.new(point1 + rightVector, diffVector + rightVector), true, {character}) local hitPart, _ = Utility.Raycast(Ray.new(point1, diffVector), true, {character}) local leftPart, _ = Utility.Raycast(Ray.new(point1 - rightVector, diffVector - rightVector), true, {character}) if rightPart or hitPart or leftPart then return false end -- Make sure we have somewhere to stand on local midPt = (point2 + point1) / 2 local studsBetweenSamples = 2 for i = 1, math_floor(diffVector.magnitude/studsBetweenSamples) do local downPart, _ = Utility.Raycast(Ray.new(point1 + directionVector * i * studsBetweenSamples, Vector3_new(0,-7,0)), true, {character}) if not downPart then return false end end return true end function this:SmoothPoints(pathToSmooth) local result = {} local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso for i = 1, #pathToSmooth do table.insert(result, pathToSmooth[i]) end -- Backwards for safe-deletion for i = #result - 1, 1, -1 do if i + 1 <= #result then local nextPoint = result[i+1] local thisPoint = result[i] local lastPoint = result[i-1] if lastPoint == nil then lastPoint = torso and Vector3_new(torso.CFrame.p.X, thisPoint.Y, torso.CFrame.p.Z) end if lastPoint and Utility.FuzzyEquals(thisPoint.Y, lastPoint.Y) and Utility.FuzzyEquals(thisPoint.Y, nextPoint.Y) then if this:CheckOcclusion(lastPoint, nextPoint, character) then table.remove(result, i) -- Move i back one to recursively-smooth i = i + 1 end end end end return result end function this:CheckNeighboringCells(character) local pathablePoints = {} local humanoid = findPlayerHumanoid(Player) local torso = character and humanoid and humanoid.Torso if torso then local torsoCFrame = torso.CFrame local torsoPos = torsoCFrame.p -- Minus and plus 2 is so we can get it into the cell-corner space and then translate it back into cell-center space local roundedPos = Vector3_new(Utility.Round(torsoPos.X-2,4)+2, Utility.Round(torsoPos.Y-2,4)+2, Utility.Round(torsoPos.Z-2,4)+2) local neighboringCells = {} for x = -4, 4, 8 do for z = -4, 4, 8 do table.insert(neighboringCells, roundedPos + Vector3_new(x,0,z)) end end for _, testPoint in pairs(neighboringCells) do local pathable = this:CheckOcclusion(roundedPos, testPoint, character, ZERO_VECTOR3) if pathable then table.insert(pathablePoints, testPoint) end end end return pathablePoints end function this:ComputeDirectPath() local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso if torso then local startPt = torso.CFrame.p local finishPt = point if (finishPt - startPt).magnitude < 150 then -- move back the destination by 2 studs or otherwise the pather will collide with the object we are trying to reach finishPt = finishPt - (finishPt - startPt).unit * 2 if this:CheckOcclusion(startPt, finishPt, character, ZERO_VECTOR3) then local pathResult = {} pathResult.Status = Enum.PathStatus.Success function pathResult:GetPointCoordinates() return {finishPt} end return pathResult end end end end local function AllAxisInThreshhold(targetPt, otherPt, threshold) return math_abs(targetPt.X - otherPt.X) <= threshold and math_abs(targetPt.Y - otherPt.Y) <= threshold and math_abs(targetPt.Z - otherPt.Z) <= threshold end function this:ComputePath() local smoothed = false local humanoid = findPlayerHumanoid(Player) local torso = humanoid and humanoid.Torso if torso then if this.PathComputed then return end this.PathComputed = true -- Will yield the script since it is an Async script (start, finish, maxDistance) -- Try to use the smooth function, but it may not exist yet :( local success = pcall(function() -- 3 is height from torso cframe to ground this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400) smoothed = true end) if not success then -- 3 is height from torso cframe to ground this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400) smoothed = false end this.pointList = this.pathResult and this.pathResult:GetPointCoordinates() local pathFound = false if this.pathResult.Status == Enum.PathStatus.FailFinishNotEmpty then -- Lets try again with a slightly set back start point; it is ok to do this again so the FailFinishNotEmpty uses little computation local diffVector = point - workspace.CurrentCamera.CoordinateFrame.p if diffVector.magnitude > 2 then local setBackPoint = point - (diffVector).unit * 2.1 local success = pcall(function() this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p, setBackPoint, 400) smoothed = true end) if not success then this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p, setBackPoint, 400) smoothed = false end this.pointList = this.pathResult and this.pathResult:GetPointCoordinates() pathFound = true end end if this.pathResult.Status == Enum.PathStatus.ClosestNoPath and #this.pointList >= 1 and pathFound == false then local otherPt = this.pointList[#this.pointList] if AllAxisInThreshhold(point, otherPt, 4) and (torso.CFrame.p - point).magnitude > (otherPt - point).magnitude then local pathResult = {} pathResult.Status = Enum.PathStatus.Success function pathResult:GetPointCoordinates() return {this.pointList} end this.pathResult = pathResult pathFound = true end end if (this.pathResult.Status == Enum.PathStatus.FailStartNotEmpty or this.pathResult.Status == Enum.PathStatus.ClosestNoPath) and pathFound == false then local pathablePoints = this:CheckNeighboringCells(character) for _, otherStart in pairs(pathablePoints) do local pathResult; local success = pcall(function() pathResult = PathfindingService:ComputeSmoothPathAsync(otherStart, point, 400) smoothed = true end) if not success then pathResult = PathfindingService:ComputeRawPathAsync(otherStart, point, 400) smoothed = false end if pathResult and pathResult.Status == Enum.PathStatus.Success then this.pathResult = pathResult if this.pathResult then this.pointList = this.pathResult:GetPointCoordinates() table.insert(this.pointList, 1, otherStart) end break end end end if DirectPathEnabled then if this.pathResult.Status ~= Enum.PathStatus.Success then local directPathResult = this:ComputeDirectPath() if directPathResult and directPathResult.Status == Enum.PathStatus.Success then this.pathResult = directPathResult this.pointList = directPathResult:GetPointCoordinates() end end end end return smoothed end function this:IsValidPath() this:ComputePath() local pathStatus = this.pathResult.Status return pathStatus == Enum.PathStatus.Success end function this:GetPathStatus() this:ComputePath() return this.pathResult.Status end function this:Start() if CurrentSeatPart then return end spawn(function() local humanoid = findPlayerHumanoid(Player) --humanoid.AutoRotate = false local torso = humanoid and humanoid.Torso if torso then if this.Started then return end this.Started = true -- Will yield the script since it is an Async function script (start, finish, maxDistance) local smoothed = this:ComputePath() if this:IsValidPath() then this.PathStarted:fire() -- smooth out zig-zaggy paths local smoothPath = smoothed and this.pointList or this:SmoothPoints(this.pointList) for i, point in pairs(smoothPath) do if humanoid then if this.Cancelled then return end local wayPoint = nil if SHOW_PATH then wayPoint = CreateDestinationIndicator(point) wayPoint.BrickColor = BrickColor.new("New Yeller") wayPoint.Parent = workspace print(wayPoint.CFrame.p) end humanoid:MoveTo(point) local distance = ((torso.CFrame.p - point) * XZ_VECTOR3).magnitude local approxTime = 10 if math_abs(humanoid.WalkSpeed) > 0 then approxTime = distance / math_abs(humanoid.WalkSpeed) end local yielding = true if i == 1 then --local rotatedCFrame = CameraModule:LookAtPreserveHeight(point) if CameraModule then local rotatedCFrame = CameraModule:LookAtPreserveHeight(smoothPath[#smoothPath]) local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame) end --CharacterControl:SetTorsoLookPoint(point) end ---[[ if (humanoid.Torso.CFrame.p - point).magnitude > 9 then spawn(function() while yielding and this.Cancelled == false do if CameraModule then local look = CameraModule:GetCameraLook() local squashedLook = (look * XZ_VECTOR3).unit local direction = ((point - CameraModule.cframe.p) * XZ_VECTOR3).unit local theta = math_deg(math_acos(squashedLook:Dot(direction))) if tick() - Utility.GetLastInput() > 2 and theta > (workspace.CurrentCamera.FieldOfView / 2) then local rotatedCFrame = CameraModule:LookAtPreserveHeight(point) local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame) --return end end wait(0.1) end end) end --]] local didReach = this:YieldUntilPointReached(character, point, approxTime * 3 + 1) yielding = false if SHOW_PATH then wayPoint:Destroy() end if not didReach then this.PathFailed:fire() return end end end this.Finished:fire() return end end this.PathFailed:fire() end) end return this end ------------------------------------------------------------------------- local function FlashRed(object) local origColor = object.BrickColor local redColor = BrickColor.new("Really red") local start = tick() local duration = 4 spawn(function() while object and tick() - start < duration do object.BrickColor = origColor wait(0.13) if object then object.BrickColor = redColor end wait(0.13) end end) end --local joystickWidth = 250 --local joystickHeight = 250 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 function ExploreWithRayCast(currentPoint, originDirection) local TestDistance = 40 local TestVectors = {} do local forwardVector = originDirection; for i = 0, 15 do table.insert(TestVectors, CFrame.Angles(0, math.pi / 8 * i, 0) * forwardVector) end end local testResults = {} -- Heuristic should be something along the lines of distance and closeness to the traveling direction local function ExploreHeuristic() for _, testData in pairs(testResults) do local walkDirection = -1 * originDirection local directionCoeff = (walkDirection:Dot(testData['Vector']) + 1) / 2 local distanceCoeff = testData['Distance'] / TestDistance testData["Value"] = directionCoeff * distanceCoeff end end for i, vec in pairs(TestVectors) do local hitPart, hitPos = Utility.Raycast(Ray.new(currentPoint, vec * TestDistance), true, {Player.Character}) if hitPos then table.insert(testResults, {Vector = vec; Distance = (hitPos - currentPoint).magnitude}) else table.insert(testResults, {Vector = vec; Distance = TestDistance}) end end ExploreHeuristic() table.sort(testResults, function(a,b) return a["Value"] > b["Value"] end) return testResults end local TapId = 1 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 DebrisService:AddItem(ExistingIndicator, 0) ExistingIndicator = nil end end local function getExtentsSize(Parts) local maxX = Parts[1].Position.X local maxY = Parts[1].Position.Y local maxZ = Parts[1].Position.Z local minX = Parts[1].Position.X local minY = Parts[1].Position.Y local minZ = Parts[1].Position.Z for i = 2, #Parts do maxX = math_max(maxX, Parts[i].Position.X) maxY = math_max(maxY, Parts[i].Position.Y) maxZ = math_max(maxZ, Parts[i].Position.Z) minX = math_min(minX, Parts[i].Position.X) minY = math_min(minY, Parts[i].Position.Y) minZ = 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 AutoJumperInstance = nil local ShootCount = 0 local FailCount = 0 local function OnTap(tapPositions, goToPoint) -- Good to remember if this is the latest tap event TapId = TapId + 1 local thisTapId = TapId 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 = Utility.GetUnitRay(tapPositions[1].x, tapPositions[1].y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera) local ray = Ray.new(unitRay.Origin, unitRay.Direction*400) local hitPart, hitPt = Utility.Raycast(ray, true, {character}) local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart) local torso = character and character:FindFirstChild("Humanoid") and character:FindFirstChild("Humanoid").Torso 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() local myHumanoid = findPlayerHumanoid(Player) if myHumanoid then myHumanoid:MoveTo(hitPt) end ShootCount = ShootCount + 1 local thisShoot = ShootCount -- Do shooot 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) if thisPather:IsValidPath() then FailCount = 0 -- TODO: Remove when bug in engine is fixed Player:Move(Vector3_new(1, 0, 0)) Player:Move(ZERO_VECTOR3) thisPather:Start() if BindableEvent_OnFailStateChanged then BindableEvent_OnFailStateChanged:Fire(false) end CleanupPath() local destinationGlobe = CreateDestinationIndicator(hitPt) destinationGlobe.Parent = camera ExistingPather = thisPather ExistingIndicator = destinationGlobe if AutoJumperInstance then AutoJumperInstance:Run() end PathCompleteListener = thisPather.Finished:connect(function() if AutoJumperInstance then AutoJumperInstance:Stop() end if destinationGlobe then if ExistingIndicator == destinationGlobe then ExistingIndicator = nil end DebrisService:AddItem(destinationGlobe, 0) destinationGlobe = nil end if hitChar then local humanoid = findPlayerHumanoid(Player) ShootCount = ShootCount + 1 local thisShoot = ShootCount -- Do shoot local currentWeapon = GetEquippedTool(character) if currentWeapon then currentWeapon:Activate() LastFired = tick() end if humanoid then humanoid:MoveTo(hitPt) end end local finishPos = torso and torso.CFrame.p --hitPt if finishPos and startPos and tick() - Utility.GetLastInput() > 2 then local exploreResults = ExploreWithRayCast(finishPos, ((startPos - finishPos) * XZ_VECTOR3).unit) -- Check for Nans etc.. if exploreResults[1] and exploreResults[1]["Vector"] and exploreResults[1]["Vector"].magnitude >= 0.5 and exploreResults[1]["Distance"] > 3 then if CameraModule then local rotatedCFrame = CameraModule:LookAtPreserveHeight(finishPos + exploreResults[1]["Vector"] * exploreResults[1]["Distance"]) local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame) end end end end) PathFailedListener = thisPather.PathFailed:connect(function() if AutoJumperInstance then AutoJumperInstance:Stop() end if destinationGlobe then FlashRed(destinationGlobe) DebrisService:AddItem(destinationGlobe, 3) end end) else if hitPt then -- Feedback here for when we don't have a good path local failedGlobe = CreateDestinationIndicator(hitPt) FlashRed(failedGlobe) DebrisService:AddItem(failedGlobe, 1) failedGlobe.Parent = camera if ExistingIndicator == nil then FailCount = FailCount + 1 if FailCount >= 3 then if BindableEvent_OnFailStateChanged then BindableEvent_OnFailStateChanged:Fire(true) end CleanupPath() end end end end elseif hitPt and character and CurrentSeatPart then local destinationGlobe = CreateDestinationIndicator(hitPt) destinationGlobe.Parent = camera ExistingIndicator = destinationGlobe DrivingTo = hitPt local ConnectedParts = CurrentSeatPart:GetConnectedParts(true) while wait() do if CurrentSeatPart and ExistingIndicator == destinationGlobe then local ExtentsSize = getExtentsSize(ConnectedParts) if inExtents(ExtentsSize, destinationGlobe.Position) then DebrisService:AddItem(destinationGlobe, 0) destinationGlobe = nil DrivingTo = nil break end else DebrisService:AddItem(destinationGlobe, 0) if CurrentSeatPart == nil and destinationGlobe == ExistingIndicator then DrivingTo = nil OnTap(tapPositions, hitPt) end destinationGlobe = nil break end end else -- no hit pt end end elseif #tapPositions >= 2 then if camera then ShootCount = ShootCount + 1 local thisShoot = ShootCount -- Do shoot local avgPoint = Utility.AveragePoints(tapPositions) local unitRay = Utility.GetUnitRay(avgPoint.x, avgPoint.y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera) 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 = ClassicCameraModule() do -- Extend The Camera Module Class function CameraModule:LookAtPreserveHeight(newLookAtPt) local camera = workspace.CurrentCamera local focus = camera.Focus.p local cameraCFrame = CameraModule.cframe local mag = Vector3_new(cameraCFrame.lookVector.x, 0, cameraCFrame.lookVector.z).magnitude local newLook = (Vector3_new(newLookAtPt.x, focus.y, newLookAtPt.z) - focus).unit * mag local flippedLook = newLook + Vector3_new(0, cameraCFrame.lookVector.y, 0) local distance = (focus - cameraCFrame.p).magnitude local newCamPos = focus - flippedLook.unit * distance return CFrame.new(newCamPos, newCamPos + flippedLook) end local lerp = CFrame.new().lerp function CameraModule:TweenCameraLook(desiredCFrame, speed) local e = 2.718281828459 local function SCurve(t) return 1/(1 + e^(-t*1.5)) end local function easeOutSine(t, b, c, d) if t >= d then return b + c end return c * math_sin(t/d * (math_pi/2)) + b; end local c0, c1 = CFrame_new(ZERO_VECTOR3, self:GetCameraLook()), desiredCFrame - desiredCFrame.p local theta = GetThetaBetweenCFrames(c0, c1) theta = clamp(0, math_pi, theta) local duration = 0.65 * SCurve(theta - math_pi/4) + 0.15 if speed then duration = theta / speed end local start = tick() local finish = start + duration self.UpdateTweenFunction = function() local currTime = tick() - start local alpha = clamp(0, 1, easeOutSine(currTime, 0, 1, duration)) local newCFrame = lerp(c0, c1, alpha) local y = findAngleBetweenXZVectors(newCFrame.lookVector, self:GetCameraLook()) if IsFinite(y) and math_abs(y) > 0.0001 then self.RotateInput = self.RotateInput + Vector2_new(y, 0) end return (currTime >= finish or alpha >= 1) end end end --- Done Extending 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) --print("Touch tap fake:" , processed) --if not processed then -- OnTap({input.Position}) --end 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) if not UIS.TouchEnabled then -- PC if AutoJumperInstance then AutoJumperInstance:Stop() AutoJumperInstance = nil end AutoJumperInstance = AutoJumper() end local function getThrottleAndSteer(object, point) local lookVector = (point - object.Position) lookVector = Vector3_new(lookVector.X, 0, lookVector.Z).unit local objectVector = Vector3_new(object.CFrame.lookVector.X, 0, object.CFrame.lookVector.Z).unit local dirVector = lookVector - objectVector local mag = dirVector.magnitude local degrees = math_deg(math_acos(lookVector:Dot(objectVector))) local side = (object.CFrame:pointToObjectSpace(point).X > 0) local throttle = 0 if mag < 0.25 then throttle = 1 end if mag > 1.8 then throttle = -1 end local distance = CurrentSeatPart.Position - DrivingTo local velocity = CurrentSeatPart.Velocity if velocity.magnitude*1.5 > distance.magnitude then if velocity.magnitude*0.5 > distance.magnitude then throttle = -throttle else throttle = 0 end end local steer = 0 if degrees > 5 and degrees < 175 then if side then steer = 1 else steer = -1 end end local rotatingAt = math_deg(CurrentSeatPart.RotVelocity.magnitude) local degreesAway = math_max(math_min(degrees, 180 - degrees), 10) if (CurrentSeatPart.RotVelocity.X < 0)== (steer < 0) then if rotatingAt*1.5 > degreesAway then if rotatingAt*0.5 > degreesAway then steer = -steer else steer = 0 end end end return throttle, steer end local function Update() if CameraModule then 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.Throttle = throttle CurrentSeatPart.Steer = steer end end end local success = pcall(function() RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update) end) if not success then if RenderSteppedConn then RenderSteppedConn:disconnect() end RenderSteppedConn = RunService.RenderStepped:connect(Update) end local WasAutoJumper = false local WasAutoJumpMobile = false local function onSeated(child, active, currentSeatPart) if active then if BindableEvent_EnableTouchJump then BindableEvent_EnableTouchJump:Fire(true) end if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then CurrentSeatPart = currentSeatPart if AutoJumperInstance then AutoJumperInstance:Stop() AutoJumperInstance = nil WasAutoJumper = true else WasAutoJumper = false end if child.AutoJumpEnabled then WasAutoJumpMobile = true child.AutoJumpEnabled = false end end else CurrentSeatPart = nil if BindableEvent_EnableTouchJump then BindableEvent_EnableTouchJump:Fire(false) end if WasAutoJumper then AutoJumperInstance = AutoJumper() WasAutoJumper = false end if WasAutoJumpMobile then child.AutoJumpEnabled = true WasAutoJumpMobile = false 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() DebrisService:AddItem(ExistingIndicator, 1) if AutoJumperInstance then AutoJumperInstance:Stop() AutoJumperInstance = nil end end) local WasAutoJumper = false local WasAutoJumpMobile = false 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 AutoJumperInstance then AutoJumperInstance:Stop() AutoJumperInstance = nil end 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